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

It is difficult to plan for layout in mixed subplots

I looked a mixed subplots example in Plotly web site here

To be honest, Plotly is really great for plot single figure with simple layout. However, when it comes to complexed mixed subplots, it is really mess when you have to make a plan on the overal layout.

In the example, it is subplots with three axes, and the nest structure of dict in the layout make me really sad. Usually I spend a lot time planning on the details of the layout.

Compared with matplotlib, it automatically generates subplots axes objects using subplots function or other related functions, then you can use related methods like imshow, plot_surface, scatter and etc to generate the figure. I’d say this is more of a python way of doing the figure. From my personal perspetive, this is more accepted in the community.

The complex nested Layout API is really the thing that make me NOT use Plolty as my main graph drawing package during my work and reseach related staff. But sometimes , I do need some 3D functionality of Plotly that matplotlib not good at.

I posted here to see if there is any other user of Plotly feel the same, and how they solve the problem.

I have four yeas of experience using matplotlib, I tried other packages like seaborn, bokeh, Plotly. In terms of API, matplotlib did a really great job. In term of 3d plot and simple layout graph, Plotly is my favorite. Discusssions are welcomed!!!

Thanks!!!

@ted930511 The example of mixed subplots at the given link doesn’t use plotly.tools.make_subplots() and the user has to set the domain for each axis. This is an example https://plot.ly/~empet/15130 on how to call tools.make_subplots(), and update the layout to meet your requirements.

@empet, thank you! It seems using layout.update() is the core in the example code. Anyway, this is much better than the example in the documentation example.

@empet, Hi, I implemented the code you provided. I really liked the way you organize the layout. One more question about using the update() function.

Is there a programmable way to correlate the traces to the corresponding axes. After appending traces to fig, I tired code below.

fig.append_trace(trace1, 1,1)
fig.append_trace(trace2, 1,2)
fig.append_trace(trace3, 2,1)
fig.append_trace(trace4, 2,1)

trace1.scene.update(...) # this do not work, because trace1.scene returns a None type

In the example code, you use:

fig.layout.scene.update(xaxis=axis3d,
                    yaxis=axis3d,
                    zaxis=axis3d,
                    camera=dict(eye=dict(x=1.4, y=1.4, z=1.4)))

xaxis refers to the 3d axis, so it will be updated. I am wondering if there is a programmable way to establish the trace to axis correspondence.

Thanks!

Hi @ted930511,

The correspondence cell-axes is displayed after fig definition:

This is the format of your plot grid:
[ (1,1) scene1 ]  [ (1,2) x1,y1 ] 
[ (2,1) x2,y2

i.e. the 3d trace should be appended to the cell(1,1), and the 2d traces to (1,2), respectively (2,1).
The layout for each cell is described in fig.layout, not in the trace definition.

fig.append_trace(tracename, i,j)

assigns the trace 'tracename` to the cell (i,j) and its layout is described by xaxis, yaxis associated to that cell.

To clarify how we make updates like fig.layout.scene.update() or fig.layout.scene2.update() I defined in this notebook https://plot.ly/~empet/15133 a new subplot, with two 3d cells and two 2d cells.

Note that you can read off the scene name for the 3d case, respectively the axes name for 2d cells from the info displayed after fig definition:

fig = tools.make_subplots(specs=[[{'is_3d': True},  {'is_3d':True}], 
                                 [{}, {} ]],
                                 
                           vertical_spacing=0.075,
                           horizontal_spacing=0.075,
                           rows=2,
                           cols=2
                         )
This is the format of your plot grid:
[ (1,1) scene1 ]  [ (1,2) scene2 ]
[ (2,1) x1,y1 ]   [ (2,2) x2,y2 ] 

The scene1 for a 3d layout and xaxis1, yaxis1 in a 2d layout can be accessed as scene,
respectively xaxis, yaxis, too (i.e. the missing index is automatically understood as 1)
(this rule can be a bit confusing).

In scene1, and scene2 the axes name is not indexed because we access them via their parent, scene.
The axes in cell (2, 1) are xaxis1, yaxis1, while in the cell (2,2) they are xaxis2, yaxis2.

Hi @ted930511,

Thanks for your feedback and thanks @empet for the good examples. I’m the maintainer of plotly.py and I totally agree that subplotting can be a pain sometimes :slightly_smiling_face:

The make_subplots API helps a lot, but it currently has the limitation that it only works for 2D and 3D cartesian subplot types (not geo, parcoords, pie, parcats, etc.).

One of my big goals for version 4 is figuring out how to make subplotting smoother. An impetus for this is that in version 4 we’ll be integrating the newly released plotly_express into plotly.py. plotly_express makes it really easy to make faceted subplots, but it’s current subplot approach isn’t compatible with plotly.py’s make_subplots approach.

As an experienced matplotlib user, do you think an improved make_subplots API that covers all trace types would feel natural to you? After the initial figure is created using make_subplots, what other subplot operations would you want to be able to perform on the figure. For example we have methods for adding traces to particular subplots, but we don’t have anything get for retrieving traces or axis objects based on subplot.

Some related issues that you may want to weigh in on:

-Jon

Hi Empet,

Thanks for sharing lots of useful tips as always

I just have a follow-up question on your demonstration to plot mixed subplots for 2D and 3D figures. It works great when you only need a y axis on each subplot. However, I believe tool.make_subplots cannot handle multiple y-axes on a subplot, correct?

So how do we create 2x1 subplots, where a 3D figure is shown on the first row, and then a subplot on 2nd row with multiple y-axes?

Hi @nqv,

You can define mixed subplots using make_subplots:

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=2, cols=1,  vertical_spacing=0.05,
    specs=[[{"type": "scene"}], [{"secondary_y": True}]])
#IMPORTANT!!!!  To decide how to update the plot layout, print fig.layout to see its initial definition
print(fig.layout)
fig.add_trace(go.Scatter3d(x=[3, 2, 5, 7],
                           y=[1, 3, 4, 6],
                           z=[4, 2, 1, 6], 
                           name='scatt3d',
                           mode='lines', 
                           marker_size=2), 1, 1);


fig.add_trace(
    go.Bar(x=[1, 2, 3, 4],
           y=[7, 4, 5, 6],
           name="bar",
           visible=True
          ), 2, 1, secondary_y=False)

fig.add_trace(
    go.Scatter(x=[1, 2, 3, 4],
               y=[4, 2, 5, 3],
               name="scatt2d",
               line_color="black"
              ), 2, 1,secondary_y=True)

fig.update_layout(width=600, height=600, yaxis_domain=[0, 0.4])
fig.update_scenes(camera_eye=dict(x=1.65, y=1.65, z=0.5))

Hi @empet,

Really appreciate your fast response.

I saw that I can specify “secondary_y” on the make_subplots and then enable secondary_y (True or False) in the fig.add_trace to add a secondary y-axis to one of the subplots. What about adding a tertiary y-axis on the right and 4th y-axis on the left? At this point, should I use the low-level API to specify yaxis, yaxis1 to yaxis4 in the fig.layout_update as suggested by this Plotly-Multiple-Axes?

Here is my the breakdown of my approach so far:

  1. Use make_subplots to make 2x1 subplot grid where a 3D plot will be on the first row and 2D plot on secondary row.
    fig =tools.make_subplots(specs=[[{‘is_3d’: True}],
    [{‘secondary_y’:True}]],

                         vertical_spacing=0.075,
                         horizontal_spacing=0.075,
                         row_heights=[0.70,0.15],
                         rows=2,
                         cols=1
                       ) 
    

I use tools.make_subplots here instead of make_subplots because I want my ‘fig’ in the Figure type instead of tuple. It’s more convenient for me right now, because I don’t have to change my code a lot to make it worked with tuple.

  1. Add traces to the figure per your suggestion
    fig.add_trace(go.Scatter3d(), row=1, col=1 )
    fig.add_trace(go.Scatter(), row=2, col=1, secondary_y=False )
    fig.add_trace(go.Scatter(), row=2, col=1, secondary_y=True )
    fig.add_trace(go.Scatter(), row=2, col=1 )
    I’m not sure what to do with the last trace. I wanted to show this trace on the secondary subplot as a tertiary y-axis on the right.

print(fig.layout) showed:
‘xaxis’: {‘anchor’: ‘y’, ‘domain’: [0.15, 0.9], ‘title’: {‘text’: ‘Depth (ft)’}},
‘yaxis’: {‘anchor’: ‘x’,
‘domain’: [0.0, 0.1632352941176471],
‘range’: [0, 14000],
‘tickfont’: {‘color’: ‘#d62728’},
‘title’: {‘font’: {‘color’: ‘#d62728’}, ‘text’: ‘Y-axis1’}},
‘yaxis2’: {‘anchor’: ‘x’,
‘linecolor’: ‘#2d6dfa’,
‘overlaying’: ‘y’,
‘range’: [0, 120],
‘side’: ‘right’,
‘tickfont’: {‘color’: ‘#2d6dfa’},
‘title’: {‘font’: {‘color’: ‘#2d6dfa’}, ‘text’: ‘Y-axis2’}},
‘yaxis3’: {‘anchor’: ‘free’,
‘linecolor’: ‘#fbcb09’,
‘overlaying’: ‘y’,
‘position’: 0.98,
‘range’: [0, 6],
‘side’: ‘right’,
‘tickfont’: {‘color’: ‘#fbcb09’},
‘title’: {‘font’: {‘color’: ‘#fbcb09’}, ‘text’: ‘Y-axis3’}},

So, I figured I need to define the yaxis4, so i called
fig.update_layout(

yaxis4=dict(
title=‘Y-axis4’,
titlefont=dict(
color=’#fbcb09
),
tickfont=dict(
color=’#fbcb09
),
anchor=‘free’,
overlaying=‘y’,
side=‘right’,
linecolor=’#fbcb09’,

    position=0.98,
    range=[0,6]

),

)
Then I indicated xaxis=‘x’ and yaxis=‘y4’ on the add_trace for the last trace. It didn’t work out either.

Can you give me more advice, please?

Thanks

@nqv

This is an example of multiple yaxes (more than two) for subplots with 1 column and two rows:

Thanks @empet

I figured it out based on the resources that you provided to me.

I found all your example codes are extremely helpful. Do you post all your examples somewhere? So I can use them as a reference for the future?

@nqv Here https://chart-studio.plotly.com/~empet#/ you can find all my plots/jupyter notebooks.

Thanks again @empet. Appreciate your time and help.