Black Lives Matter. Please consider donating to Black Girls Code today.

Introspecting Figures in Python

How to dig into and learn more about the figure data structure.


If you're using Dash Enterprise's Data Science Workspaces, you can copy/paste any of these cells into a Workspace Jupyter notebook.
Alternatively, download this entire tutorial as a Jupyter notebook and import it into your Workspace.
Find out if your company is using Dash Enterprise.

Install Dash Enterprise on Azure | Install Dash Enterprise on AWS


New to Plotly?

Plotly is a free and open-source graphing library for Python. We recommend you read our Getting Started guide for the latest installation or upgrade instructions, then move on to our Plotly Fundamentals tutorials or dive straight in to some Basic Charts tutorials.

The Figure Lifecycle

As explained in the Figure Data Structure documentation, when building a figure object with Plotly.py, it is not necessary to populate every possible attribute. At render-time, figure objects (whether generated via Plotly Express or Graph Objects) are passed from Plotly.py to Plotly.js, which is the Javascript library responsible for turning JSON descriptions of figures into graphical representations.

As part of this rendering process, Plotly.js will determine, based on the attributes that have been set, which other attributes require values in order to draw the figure. Plotly.js will then apply either static or dynamic defaults to all of the remaining required attributes and render the figure. A good example of a static default would be the text font size: if unspecified, the default value is always the same. A good example of a dynamic default would be the range of an axis: if unspecified, the default will be computed based on the range of the data in traces associated with that axis.

Introspecting Plotly Express Figures

Figure objects created by Plotly Express have a number of attributes automatically set, and these can be introspected using the Python print() function, or in JupyterLab, the special fig.show("json") renderer, which gives an interactive drilldown interface with search:

In [1]:
import plotly.express as px

fig = px.scatter(x=[10, 20], y=[20, 10], height=400, width=400)
fig.show()
print(fig)
Figure({
    'data': [{'hovertemplate': 'x=%{x}<br>y=%{y}<extra></extra>',
              'legendgroup': '',
              'marker': {'color': '#636efa', 'symbol': 'circle'},
              'mode': 'markers',
              'name': '',
              'orientation': 'v',
              'showlegend': False,
              'type': 'scatter',
              'x': array([10, 20]),
              'xaxis': 'x',
              'y': array([20, 10]),
              'yaxis': 'y'}],
    'layout': {'height': 400,
               'legend': {'tracegroupgap': 0},
               'margin': {'t': 60},
               'template': '...',
               'width': 400,
               'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'x'}},
               'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'y'}}}
})

We can learn more about the attributes Plotly Express has set for us with the Python help() function:

In [2]:
help(fig.data[0].__class__.mode)
Help on property:

    Determines the drawing mode for this scatter trace. If the
    provided `mode` includes "text" then the `text` elements appear
    at the coordinates. Otherwise, the `text` elements appear on
    hover. If there are less than 20 points and the trace is not
    stacked then the default is "lines+markers". Otherwise,
    "lines".
    
    The 'mode' property is a flaglist and may be specified
    as a string containing:
      - Any combination of ['lines', 'markers', 'text'] joined with '+' characters
        (e.g. 'lines+markers')
        OR exactly one of ['none'] (e.g. 'none')
    
    Returns
    -------
    Any

Accessing Javascript-Computed Defaults

new in 4.10

The .full_figure_for_development() method provides Python-level access to the default values computed by Plotly.js. This method requires the Kaleido package, which is easy to install and also used for static image export.

By way of example, here is an extremely simple figure created with Graph Objects (although it could have been made with Plotly Express as well just like above) where we have disabled the default template for maximum readability. Note how in this figure the text labels on the markers are clipped, and sit on top of the markers.

In [3]:
import plotly.graph_objects as go

fig = go.Figure(
    data=[go.Scatter(
        mode="markers+text",
        x=[10,20],
        y=[20, 10],
        text=["Point A", "Point B"]
    )],
    layout=dict(height=400, width=400, template="none")
)
fig.show()

Let's print this figure to see the very small JSON object that is passed to Plotly.js as input:

In [4]:
print(fig)
Figure({
    'data': [{'mode': 'markers+text', 'text': ['Point A', 'Point B'], 'type': 'scatter', 'x': [10, 20], 'y': [20, 10]}],
    'layout': {'height': 400, 'template': '...', 'width': 400}
})

Now let's look at the "full" figure after Plotly.js has computed the default values for every necessary attribute.

Heads-up: the full figure is quite long and intimidating, and this page is meant to help demystify things so please read on!

Please also note that the .full_figure_for_development() function is really meant for interactive learning and debugging, rather than production use, hence its name and the warning it produces by default, which you can see below, and which can be supressed with warn=False.

In [5]:
full_fig = fig.full_figure_for_development()
print(full_fig)
/home/circleci/project/doc/venv/lib/python3.7/site-packages/plotly/io/_kaleido.py:304: UserWarning:

full_figure_for_development is not recommended or necessary for production use in most circumstances. 
To suppress this warning, set warn=False

Figure({
    'data': [{'cliponaxis': True,
              'error_x': {'visible': False},
              'error_y': {'visible': False},
              'fill': 'none',
              'hoverinfo': 'x+y+z+text',
              'hoverlabel': {'align': 'auto', 'font': {'family': 'Arial, sans-serif', 'size': 13}, 'namelength': 15},
              'hoveron': 'points',
              'hovertemplate': '',
              'hovertext': '',
              'legendgroup': '',
              'marker': {'color': '#1f77b4',
                         'gradient': {'type': 'none'},
                         'line': {'color': '#444', 'width': 0},
                         'maxdisplayed': 0,
                         'opacity': 1,
                         'size': 6,
                         'symbol': 'circle'},
              'mode': 'markers+text',
              'name': 'trace 0',
              'opacity': 1,
              'selected': {'marker': {'opacity': 1}},
              'showlegend': True,
              'stackgroup': '',
              'text': [Point A, Point B],
              'textfont': {'color': '#444', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 12},
              'textposition': 'middle center',
              'texttemplate': '',
              'type': 'scatter',
              'uid': '725779',
              'unselected': {'marker': {'opacity': 0.2}},
              'visible': True,
              'x': [10, 20],
              'xaxis': 'x',
              'xcalendar': 'gregorian',
              'xperiod': 0,
              'y': [20, 10],
              'yaxis': 'y',
              'ycalendar': 'gregorian',
              'yperiod': 0}],
    'layout': {'activeshape': {'fillcolor': 'rgb(255,0,255)', 'opacity': 0.5},
               'autosize': False,
               'autotypenumbers': 'convert types',
               'calendar': 'gregorian',
               'clickmode': 'event',
               'colorscale': {'diverging': [[0, 'rgb(5,10,172)'], [0.35,
                                            'rgb(106,137,247)'], [0.5,
                                            'rgb(190,190,190)'], [0.6,
                                            'rgb(220,170,132)'], [0.7,
                                            'rgb(230,145,90)'], [1,
                                            'rgb(178,10,28)']],
                              'sequential': [[0, 'rgb(220,220,220)'], [0.2,
                                             'rgb(245,195,157)'], [0.4,
                                             'rgb(245,160,105)'], [1,
                                             'rgb(178,10,28)']],
                              'sequentialminus': [[0, 'rgb(5,10,172)'], [0.35,
                                                  'rgb(40,60,190)'], [0.5,
                                                  'rgb(70,100,245)'], [0.6,
                                                  'rgb(90,120,245)'], [0.7,
                                                  'rgb(106,137,247)'], [1,
                                                  'rgb(220,220,220)']]},
               'colorway': [#1f77b4, #ff7f0e, #2ca02c, #d62728, #9467bd, #8c564b,
                            #e377c2, #7f7f7f, #bcbd22, #17becf],
               'computed': {'margin': {'b': 80, 'l': 80, 'r': 80, 't': 100}},
               'dragmode': 'zoom',
               'font': {'color': '#444', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 12},
               'height': 400,
               'hidesources': False,
               'hoverdistance': 20,
               'hoverlabel': {'align': 'auto', 'font': {'family': 'Arial, sans-serif', 'size': 13}, 'namelength': 15},
               'hovermode': 'x',
               'margin': {'autoexpand': True, 'b': 80, 'l': 80, 'pad': 0, 'r': 80, 't': 100},
               'modebar': {'activecolor': 'rgba(68, 68, 68, 0.7)',
                           'bgcolor': 'rgba(255, 255, 255, 0.5)',
                           'color': 'rgba(68, 68, 68, 0.3)',
                           'orientation': 'h'},
               'newshape': {'drawdirection': 'diagonal',
                            'fillcolor': 'rgba(0,0,0,0)',
                            'fillrule': 'evenodd',
                            'layer': 'above',
                            'line': {'color': '#444', 'dash': 'solid', 'width': 4},
                            'opacity': 1},
               'paper_bgcolor': '#fff',
               'plot_bgcolor': '#fff',
               'separators': '.,',
               'showlegend': False,
               'spikedistance': 20,
               'template': '...',
               'title': {'font': {'color': '#444', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 17},
                         'pad': {'b': 0, 'l': 0, 'r': 0, 't': 0},
                         'text': 'Click to enter Plot title',
                         'x': 0.5,
                         'xanchor': 'auto',
                         'xref': 'container',
                         'yanchor': 'auto',
                         'yref': 'container'},
               'uniformtext': {'mode': False},
               'width': 400,
               'xaxis': {'anchor': 'y',
                         'automargin': False,
                         'autorange': True,
                         'autotypenumbers': 'convert types',
                         'color': '#444',
                         'constrain': 'range',
                         'constraintoward': 'center',
                         'domain': [0, 1],
                         'dtick': 5,
                         'exponentformat': 'B',
                         'fixedrange': False,
                         'gridcolor': 'rgb(238, 238, 238)',
                         'gridwidth': 1,
                         'hoverformat': '',
                         'layer': 'above traces',
                         'minexponent': 3,
                         'nticks': 0,
                         'range': [9.244604316546763, 20.755395683453237],
                         'rangemode': 'normal',
                         'separatethousands': False,
                         'showexponent': 'all',
                         'showgrid': True,
                         'showline': False,
                         'showspikes': False,
                         'showticklabels': True,
                         'side': 'bottom',
                         'tick0': 0,
                         'tickfont': {'color': '#444', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 12},
                         'tickformat': '',
                         'ticklabelposition': 'outside',
                         'tickmode': 'auto',
                         'tickprefix': '',
                         'ticks': '',
                         'ticksuffix': '',
                         'title': {'font': {'color': '#444',
                                            'family': '"Open Sans", verdana, arial, sans-serif',
                                            'size': 14},
                                   'text': 'Click to enter X axis title'},
                         'type': 'linear',
                         'visible': True,
                         'zeroline': True,
                         'zerolinecolor': '#444',
                         'zerolinewidth': 1},
               'yaxis': {'anchor': 'x',
                         'automargin': False,
                         'autorange': True,
                         'autotypenumbers': 'convert types',
                         'color': '#444',
                         'constrain': 'range',
                         'constraintoward': 'middle',
                         'domain': [0, 1],
                         'dtick': 2,
                         'exponentformat': 'B',
                         'fixedrange': False,
                         'gridcolor': 'rgb(238, 238, 238)',
                         'gridwidth': 1,
                         'hoverformat': '',
                         'layer': 'above traces',
                         'minexponent': 3,
                         'nticks': 0,
                         'range': [9.225721784776903, 20.7742782152231],
                         'rangemode': 'normal',
                         'separatethousands': False,
                         'showexponent': 'all',
                         'showgrid': True,
                         'showline': False,
                         'showspikes': False,
                         'showticklabels': True,
                         'side': 'left',
                         'tick0': 0,
                         'tickfont': {'color': '#444', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 12},
                         'tickformat': '',
                         'ticklabelposition': 'outside',
                         'tickmode': 'auto',
                         'tickprefix': '',
                         'ticks': '',
                         'ticksuffix': '',
                         'title': {'font': {'color': '#444',
                                            'family': '"Open Sans", verdana, arial, sans-serif',
                                            'size': 14},
                                   'text': 'Click to enter Y axis title'},
                         'type': 'linear',
                         'visible': True,
                         'zeroline': True,
                         'zerolinecolor': '#444',
                         'zerolinewidth': 1}}
})

As you can see, Plotly.js does a lot of work filling things in for us! Let's look at the examples described at the top of the page of static and dynamic defaults. If we look just at layout.font and layout.xaxis.range we can see that the static default font size is 12 and that the dynamic default range is computed to be a bit beyond the data range which was 10-20:

In [6]:
print("full_fig.layout.font.size: ", full_fig.layout.font.size)
print("full_fig.layout.xaxis.range: ", full_fig.layout.xaxis.range)
full_fig.layout.font.size:  12
full_fig.layout.xaxis.range:  (9.244604316546763, 20.755395683453237)

Learning About Attributes

What else can we use this full_fig for? Let's start by looking at the first entry of the data

In [7]:
print(full_fig.data[0])
Scatter({
    'cliponaxis': True,
    'error_x': {'visible': False},
    'error_y': {'visible': False},
    'fill': 'none',
    'hoverinfo': 'x+y+z+text',
    'hoverlabel': {'align': 'auto', 'font': {'family': 'Arial, sans-serif', 'size': 13}, 'namelength': 15},
    'hoveron': 'points',
    'hovertemplate': '',
    'hovertext': '',
    'legendgroup': '',
    'marker': {'color': '#1f77b4',
               'gradient': {'type': 'none'},
               'line': {'color': '#444', 'width': 0},
               'maxdisplayed': 0,
               'opacity': 1,
               'size': 6,
               'symbol': 'circle'},
    'mode': 'markers+text',
    'name': 'trace 0',
    'opacity': 1,
    'selected': {'marker': {'opacity': 1}},
    'showlegend': True,
    'stackgroup': '',
    'text': [Point A, Point B],
    'textfont': {'color': '#444', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 12},
    'textposition': 'middle center',
    'texttemplate': '',
    'uid': '725779',
    'unselected': {'marker': {'opacity': 0.2}},
    'visible': True,
    'x': [10, 20],
    'xaxis': 'x',
    'xcalendar': 'gregorian',
    'xperiod': 0,
    'y': [20, 10],
    'yaxis': 'y',
    'ycalendar': 'gregorian',
    'yperiod': 0
})

We see that this is an instance of go.Scatter (as expected, given the input) and that it has an attribute we've maybe never heard of called cliponaxis which by default seems to be set to True in this case. Let's find out more about this attribute using the built-in Python help() function

In [8]:
help(go.Scatter.cliponaxis)
Help on property:

    Determines whether or not markers and text nodes are clipped
    about the subplot axes. To show markers and text nodes above
    axis lines and tick labels, make sure to set `xaxis.layer` and
    `yaxis.layer` to *below traces*.
    
    The 'cliponaxis' property must be specified as a bool
    (either True, or False)
    
    Returns
    -------
    bool

Aha! This explains why in our original figure above, the text was cut off by the edge of the plotting area! Let's try forcing that to False, and let's also use the attribute textposition which we see in the full figure is by default set to "middle center" to get our text off of our markers:

In [9]:
fig.update_traces(cliponaxis=False, textposition="top right")
fig.show()

We can use this technique (of making a figure, and querying Plotly.js for the "full" version of that figure, and then exploring the attributes that are automatically set for us) to learn more about the range of possibilities that the figure schema makes available. We can drill down into layout attributes also:

In [10]:
help(go.layout.XAxis.autorange)
Help on property:

    Determines whether or not the range of this axis is computed in
    relation to the input data. See `rangemode` for more info. If
    `range` is provided, then `autorange` is set to False.
    
    The 'autorange' property is an enumeration that may be specified as:
      - One of the following enumeration values:
            [True, False, 'reversed']
    
    Returns
    -------
    Any

More about Layout

In the figure we introspected above, we had added a scatter trace, and Plotly.js automatically filled in for us the xaxis and yaxis values of that trace object to be x and y, and then also filled out the corresponding layout.xaxis and layout.yaxis objects for us, complete with their extensive set of defaults for gridlines, tick labels and so on.

If we create a figure with a scattergeo trace instead, however, Plotly.js will fill in a totally different set of objects in layout, corresponding to a geo subplot, with all of its defaults for whether or not to show rivers, lakes, country borders, coastlines etc.

In [11]:
import plotly.graph_objects as go

fig = go.Figure(
    data=[go.Scattergeo(
        mode="markers+text",
        lat=[10, 20],
        lon=[20, 10],
        text=["Point A", "Point B"]
    )],
    layout=dict(height=400, width=400,
                margin=dict(l=0,r=0,b=0,t=0),
                template="none")
)
fig.show()
full_fig = fig.full_figure_for_development()
print(full_fig)
Figure({
    'data': [{'fill': 'none',
              'geo': 'geo',
              'hoverinfo': 'lon+lat+location+text',
              'hoverlabel': {'align': 'auto', 'font': {'family': 'Arial, sans-serif', 'size': 13}, 'namelength': 15},
              'hovertemplate': '',
              'hovertext': '',
              'lat': [10, 20],
              'legendgroup': '',
              'lon': [20, 10],
              'marker': {'color': '#1f77b4',
                         'gradient': {'type': 'none'},
                         'line': {'color': '#444', 'width': 0},
                         'opacity': 1,
                         'size': 6,
                         'symbol': 'circle'},
              'mode': 'markers+text',
              'name': 'trace 0',
              'opacity': 1,
              'selected': {'marker': {'opacity': 1}},
              'showlegend': True,
              'text': [Point A, Point B],
              'textfont': {'color': '#444', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 12},
              'textposition': 'middle center',
              'texttemplate': '',
              'type': 'scattergeo',
              'uid': '139234',
              'unselected': {'marker': {'opacity': 0.2}},
              'visible': True}],
    'layout': {'activeshape': {'fillcolor': 'rgb(255,0,255)', 'opacity': 0.5},
               'autosize': False,
               'autotypenumbers': 'convert types',
               'calendar': 'gregorian',
               'clickmode': 'event',
               'colorscale': {'diverging': [[0, 'rgb(5,10,172)'], [0.35,
                                            'rgb(106,137,247)'], [0.5,
                                            'rgb(190,190,190)'], [0.6,
                                            'rgb(220,170,132)'], [0.7,
                                            'rgb(230,145,90)'], [1,
                                            'rgb(178,10,28)']],
                              'sequential': [[0, 'rgb(220,220,220)'], [0.2,
                                             'rgb(245,195,157)'], [0.4,
                                             'rgb(245,160,105)'], [1,
                                             'rgb(178,10,28)']],
                              'sequentialminus': [[0, 'rgb(5,10,172)'], [0.35,
                                                  'rgb(40,60,190)'], [0.5,
                                                  'rgb(70,100,245)'], [0.6,
                                                  'rgb(90,120,245)'], [0.7,
                                                  'rgb(106,137,247)'], [1,
                                                  'rgb(220,220,220)']]},
               'colorway': [#1f77b4, #ff7f0e, #2ca02c, #d62728, #9467bd, #8c564b,
                            #e377c2, #7f7f7f, #bcbd22, #17becf],
               'computed': {'margin': {'b': 0, 'l': 0, 'r': 0, 't': 0}},
               'dragmode': 'pan',
               'font': {'color': '#444', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 12},
               'geo': {'bgcolor': '#fff',
                       'center': {'lat': 0, 'lon': 0},
                       'coastlinecolor': '#444',
                       'coastlinewidth': 1,
                       'domain': {'x': [0, 1], 'y': [0, 1]},
                       'fitbounds': False,
                       'framecolor': '#444',
                       'framewidth': 1,
                       'lataxis': {'dtick': 10, 'range': [-90, 90], 'showgrid': False, 'tick0': 0},
                       'lonaxis': {'dtick': 30, 'range': [-180, 180], 'showgrid': False, 'tick0': 0},
                       'projection': {'rotation': {'lat': 0, 'lon': 0, 'roll': 0}, 'scale': 1, 'type': 'equirectangular'},
                       'resolution': 110,
                       'scope': 'world',
                       'showcoastlines': True,
                       'showcountries': False,
                       'showframe': True,
                       'showlakes': False,
                       'showland': False,
                       'showocean': False,
                       'showrivers': False,
                       'visible': True},
               'height': 400,
               'hidesources': False,
               'hoverdistance': 20,
               'hoverlabel': {'align': 'auto', 'font': {'family': 'Arial, sans-serif', 'size': 13}, 'namelength': 15},
               'hovermode': 'closest',
               'margin': {'autoexpand': True, 'b': 0, 'l': 0, 'pad': 0, 'r': 0, 't': 0},
               'modebar': {'activecolor': 'rgba(68, 68, 68, 0.7)',
                           'bgcolor': 'rgba(255, 255, 255, 0.5)',
                           'color': 'rgba(68, 68, 68, 0.3)',
                           'orientation': 'h'},
               'newshape': {'drawdirection': 'diagonal',
                            'fillcolor': 'rgba(0,0,0,0)',
                            'fillrule': 'evenodd',
                            'layer': 'above',
                            'line': {'color': '#444', 'dash': 'solid', 'width': 4},
                            'opacity': 1},
               'paper_bgcolor': '#fff',
               'separators': '.,',
               'showlegend': False,
               'spikedistance': 20,
               'template': '...',
               'title': {'font': {'color': '#444', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 17},
                         'pad': {'b': 0, 'l': 0, 'r': 0, 't': 0},
                         'text': 'Click to enter Plot title',
                         'x': 0.5,
                         'xanchor': 'auto',
                         'xref': 'container',
                         'yanchor': 'auto',
                         'yref': 'container'},
               'uniformtext': {'mode': False},
               'width': 400}
})

If I then set showrivers=True and re-query the full figure, I see that new keys have appeared in the layout.geo object for rivercolor and riverwidth, showing the dynamic nature of these defaults.

In [12]:
fig.update_geos(showrivers=True)
full_fig = fig.full_figure_for_development()
print(full_fig.layout.geo)
layout.Geo({
    'bgcolor': '#fff',
    'center': {'lat': 0, 'lon': 0},
    'coastlinecolor': '#444',
    'coastlinewidth': 1,
    'domain': {'x': [0, 1], 'y': [0, 1]},
    'fitbounds': False,
    'framecolor': '#444',
    'framewidth': 1,
    'lataxis': {'dtick': 10, 'range': [-90, 90], 'showgrid': False, 'tick0': 0},
    'lonaxis': {'dtick': 30, 'range': [-180, 180], 'showgrid': False, 'tick0': 0},
    'projection': {'rotation': {'lat': 0, 'lon': 0, 'roll': 0}, 'scale': 1, 'type': 'equirectangular'},
    'resolution': 110,
    'rivercolor': '#3399FF',
    'riverwidth': 1,
    'scope': 'world',
    'showcoastlines': True,
    'showcountries': False,
    'showframe': True,
    'showlakes': False,
    'showland': False,
    'showocean': False,
    'showrivers': True,
    'visible': True
})
In [ ]:
 

What About Dash?

Dash is an open-source framework for building analytical applications, with no Javascript required, and it is tightly integrated with the Plotly graphing library.

Learn about how to install Dash at https://dash.plot.ly/installation.

Everywhere in this page that you see fig.show(), you can display the same figure in a Dash application by passing it to the figure argument of the Graph component from the built-in dash_core_components package like this:

import plotly.graph_objects as go # or plotly.express as px
fig = go.Figure() # or any Plotly Express function e.g. px.bar(...)
# fig.add_trace( ... )
# fig.update_layout( ... )

import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()
app.layout = html.Div([
    dcc.Graph(figure=fig)
])

app.run_server(debug=True, use_reloader=False)  # Turn off reloader if inside Jupyter