Can Subplot support multiple y-axes?

Hi,

I would like to plot multiple graphs, one of the graph involves multiple y-axes. In the subplot point of view, what is the y-axis sequence? I tried many combination but the y-axis2 - y-axis4 are still missing.

Below is the sample code for reference:

import plotly.graph_objects as go

fig = go.Figure()
fig = make_subplots(
rows=1, cols=4,
specs=[[{“rowspan”: 1, “colspan”: 2}, None, {“rowspan”: 1, “colspan”: 2}, None]],
horizontal_spacing =0.1, vertical_spacing =0.09)

fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 52, 62]), row=1, col=1)

fig.add_trace(go.Bar(x=[[‘A’, ‘A’, ‘A’], [1, 2, 3]], y=[4, 5, 60000], name=“yaxis1 data”, yaxis=“y2”), row=1, col=3)
fig.add_trace(go.Bar(x=[[‘B’, ‘B’, ‘B’], [2, 3, 4]], y=[40, 50, 60], name=“yaxis2 data”, yaxis=“y5”), row=1, col=3)
fig.add_trace(go.Bar(x=[[‘C’, ‘C’, ‘C’], [4, 5, 6]], y=[40000, 50000, 60000], name=“yaxis3 data”, yaxis=“y6”), row=1, col=3)
fig.add_trace(go.Bar(x=[[‘D’, ‘D’, ‘D’], [5, 6, 7]], y=[400000, 500000, 600000], name=“yaxis4 data”, yaxis=“y7”), row=1, col=3)

# Create axis objects

fig.update_layout(xaxis2=dict(domain=[0.6, 0.9]),
yaxis2=dict(title=“yaxis1 title”, titlefont=dict(color="#1f77b4"), tickfont=dict(color="#1f77b4")),
yaxis5=dict(title=“yaxis2 title”,titlefont=dict(color="#ff7f0e"),tickfont=dict(color="#ff7f0e"), anchor=“free”, overlaying=“y2”, side=“left”, position=0.5),
yaxis6=dict(title=“yaxis3 title”,titlefont=dict(color="#d62728"),tickfont=dict(color="#d62728"), anchor=“x2”,overlaying=“y2”, side=“right”),
yaxis7=dict(title=“yaxis4 title”,titlefont=dict(color="#9467bd"),tickfont=dict(color="#9467bd"), anchor=“free”,overlaying=“y2”, side=“right”,position=1))

fig.update_layout(width=1600, height=500)
fig.update_xaxes(rangeslider_visible=False)
fig.show()

Thanks for your advice.

BR,
Jason Tam

Hi, and thanks for using the forum!

I think that you might be able to take advantage of the shared_yaxes attribute of the make_subplots() function, which is documented at https://plotly.com/python/subplots/#subplots-with-shared-yaxes.

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

fig = make_subplots(rows=2, cols=2, shared_yaxes=True)

fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 3, 4]),
              row=1, col=1)

fig.add_trace(go.Scatter(x=[20, 30, 40], y=[5, 5, 5]),
              row=1, col=2)

fig.add_trace(go.Scatter(x=[2, 3, 4], y=[600, 700, 800]),
              row=2, col=1)

fig.add_trace(go.Scatter(x=[4000, 5000, 6000], y=[7000, 8000, 9000]),
              row=2, col=2)

fig.update_layout(height=600, width=600,
                  title_text="Multiple Subplots with Shared Y-Axes")
fig.show()

I hope this helps!

Hi @Jason.Tam

When you define a figure with make_subplots() it is recommended to display fig.layout to inspect the axes name for each subplot:
(see also my answer, Can Plotly support 2 x-axis and 2 y-axis in one graph?, to your question related to secondary yaxis):

fig = make_subplots(
          rows=1, cols=4,
          specs=[[{'rowspan': 1, 'colspan': 2}, None, {'rowspan': 1, 'colspan': 2}, None]],
          horizontal_spacing =0.1)

print(fig.layout)

You’ll get:

Layout({
    'template': '...',
    'xaxis': {'anchor': 'y', 'domain': [0.0, 0.45]},
    'xaxis2': {'anchor': 'y2', 'domain': [0.55, 1.0]},
    'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0]},
    'yaxis2': {'anchor': 'x2', 'domain': [0.0, 1.0]}
})

When you add traces you don’t have to set xaxis or yaxis for each subplot. It is sufficient to give the row and col for that subplot because the axes
have been already assigned by make_subplots().

Hence in your next lines of code remove yaxis=‘y4’, ‘y5’, because they DO NOT EXIST in your subplots!!!

From now on, please, paste here on forum formatted code, because copying your code and pasting it in my notebook requires a lot of wrong character replacement.
Just check it!!!

Here are details on how to include a code block on this forum:
https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks

Hi @empet and @joseph.damiba,

Thanks for your advice.

Actually I would like to plot a multiple y-axes graph in subplot. I have checked many examples in subplot graphs, all examples in subplot graphs are related to secondary y-axes only, I am not sure whether subplot can support multiple y-axes like this-

Below is my sample code in subplot, I don’t know how to declare y-axis3, y-axis4 and y-axis5:

import plotly.graph_objects as go

fig = go.Figure()
fig = make_subplots(
    rows=1, cols=2,
    specs=[[{"rowspan": 1, "colspan": 1}, {"rowspan": 1, "colspan": 1}]],
    horizontal_spacing =0.1)

fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 52, 62]), row=1, col=1)

fig.add_trace(go.Bar(x=[['A', 'A', 'A'], [1, 2, 3]], y=[4, 5, 6000], name="A"), row=1, col=2)
fig.add_trace(go.Bar(x=[['B', 'B', 'B'], [2, 3, 4]], y=[40, 50, 60], name="B"), row=1, col=2)
fig.add_trace(go.Bar(x=[['C', 'C', 'C'], [4, 5, 6]], y=[40000, 50000, 60000], name="C"), row=1, col=2)
fig.add_trace(go.Bar(x=[['D', 'D', 'D'], [5, 6, 7]], y=[400000, 500000, 600000], name="D"), row=1, col=2)

# # Create axis objects
fig.update_layout(xaxis2=dict(domain=[0.6, 0.9]),
    yaxis2=dict(title="yaxis1 title",titlefont=dict(color="red"), tickfont=dict(color="red")),
    yaxis3=dict(title="yaxis2 title",titlefont=dict(color="orange"),tickfont=dict(color="orange"), anchor="free", overlaying="y2", side="left", position=0.5),
    yaxis4=dict(title="yaxis3 title",titlefont=dict(color="pink"),tickfont=dict(color="pink"), anchor="x2",overlaying="y2", side="right"),
    yaxis5=dict(title="yaxis4 title",titlefont=dict(color="cyan"),tickfont=dict(color="cyan"), anchor="free",overlaying="y2", side="right",position=1))

fig.update_layout(width=1600, height=400)
fig.update_xaxes(rangeslider_visible=False)
fig.show()

BR,
Jason

1 Like

@Jason.Tam

Yes, you can define such a subplot. Later I’ll post an example (approx 3 hours).

Without the posted image I couldn’t deduce from your question that you are referring to attaching more yaxes to the same plot window.

1 Like

@Jason.Tam

make_subplots was not devised to define subplots that can be referenced to multiple left and right yaxes.
Hence such subplots can be defined only with a low level code.

This is the Jupyter Notebook that defines subplots of rows=1, cols>1:
https://plotly.com/~empet/15656


and this one defines subplots of rows>1, cols=1:
https://plotly.com/~empet/15657

3 Likes

Hi @empet,

Thanks for your solution.

BR,
Jason

1 Like

Thank you very much for the solution

1 Like

I found other solution from the git-hub issues that were indicated by @josesph.damiba

fig.update_yaxes(showticklabels=True, col=2) # assuming second facet charts

This works like a charm, when using facet_cols options. (Updating here, so those searching may find it easily)

I would like to do the same thing but with an x shared axes
How is it possible? @empet

Hi, your solutions lead to a 404 now!

@idk I removed that solution because meanwhile have been added updates to plotly.py that makes the multiple yaxes definition more simple. When I gave that answer there existed no facilities, and I worked in low plotly code.

Thank you for the reply. Can you give me a quick pointer to where I can learn to do this; I’m unable to find a solution! :slight_smile:

@empet I just tried for an embarrassing amount of time to get it working based on the plotly docs (https://plotly.com/python/multiple-axes/), but couldn’t figure it out.

My understanding is that if you want e.g. three Y-axis on the second row subplot, you have to create a figure with four subplots, then tell plotly through the ‘overlaying’ figure.layout parameter that you want to display the third and fourth subplots over the top of the second one. Then you have to tell plotly through some magic usage of the ‘autoshift’ parameter that the axis labels shouldn’t overlay each other. Here is a minimal example:

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

fig = go.Figure()

fig = make_subplots(rows=4, cols=1) #, specs=[[{"secondary_y": True}]]*4)

fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6], name="yaxis data", yaxis='y1'), row=1, col=1)
fig.add_trace(go.Scatter(x=[1, 3, 4], y=[40, 50, 10], name="yaxis2 data", yaxis='y2'), row=2, col=1)
fig.add_trace(go.Scatter(x=[1, 5, 6], y=[1000, 2000, 3000], name="yaxis3 data", yaxis='y3'), row=3, col=1)
fig.add_trace(go.Scatter(x=[1, 5, 6], y=[777, 111, 222], name="yaxis4 data", yaxis='y4'), row=4, col=1)

fig.update_layout(
    yaxis=dict(
        title="yaxis title",
    ),
    yaxis2=dict(
        title="yaxis2 title",
        side="left",
        autoshift=True
    ),
    yaxis3=dict(
        title="yaxis3 title", 
        side="left",
        overlaying="y2",
        autoshift=True
    ),
    yaxis4=dict(
        title="yaxis4 title", 
        side="right",
        overlaying="y2"
    ),
)

fig.show()

This produces the following plot, where the two rows only take up half of the figure instead of the full figure, and also only the data for the y4 axis that was (I assume) overlayed last is shown.

For the avoidance of doubt, I am trying to achieve the following:

Thanks so much for any help :slight_smile:
Harry

Sorry, I’m traveling and can’t answer your question until next Monday. Maybe another Plotly user is trying in the meantime to figure out how to create a multi-yaxis in subplots.

Ah okay, thank you for getting back to me. I eagerly await your response if you’re able to solve it!! :slight_smile:

Hi @idk

Following Plotly indexing rule, subplot from col=1, row=1 is referenced to (xaxis, yaxis), and row=2, col=1 to (xaxis2, yaxis2), (xaxis2, yaxis3), (xaxis2, yaxis4).
Do you want shared yaxes y2, y3, y4, or shared xaxes x=x1, x2?
In the former case you don’t need multiple yaxes. Just one ,yaxis2, is sufficient, as long as all data have the same y range.

Hi @empet

Thanks for getting back to me. I would like four separate y ranges (yaxis1 on row=1 col=1, and yaxis2 != yaxis3 != yaxis4 on row=2, col=1). All data shares the same x range (x1=x2).

@idk
There are a lot of yaxes settings, but they are intuitive. As an idea, if you have more than two yaxes, you must restrict xaxis and xaxis2 domain from [0,1], to a smaller one. I took here [0.3, 1]:

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

fig = make_subplots(rows=2, cols=1, vertical_spacing=0.05, shared_xaxes=True)

fig.add_trace(go.Scatter(x=np.linspace(1, 7, 12), y=2+np.random.rand(12)), row=1, col=1)
fig.add_trace(go.Scatter(
        x=np.linspace(1, 7, 12), y=-2+3*np.random.rand(12),
        name='y2 data',
        yaxis='y2',
        xaxis='x2'
         ))
fig.add_trace(go.Scatter(
        x=np.linspace(1, 7, 12), y=1.5*np.random.rand(12),
        name='y3 data',
        yaxis='y3',
        xaxis='x2'
         ))
fig.add_trace(go.Scatter(
        x=np.linspace(1, 7, 12), y=1+2.3*np.random.rand(12),
        name='y4 data',
        yaxis='y4',
        xaxis='x2'
         ))

fig.update_layout(xaxis_domain= [0.3, 1], xaxis2_title='common xaxis_title',
                  xaxis2_domain=[0.3, 1],
                  yaxis2=dict(title_text='yaxis2 title'),
                  yaxis3=dict(anchor= 'free',
                              overlaying= 'y2',
                              side= 'left',
                              position= 0.15,
                              title_text='yaxis3 title'),
                  yaxis4=dict(anchor= 'x2',
                              overlaying= 'y2',
                              side ='right',
                              title_text='yaxis4 title'))
1 Like

Thanks so much, that did the trick!! Looks like my main issue was specifying row=i, col=1 in each call to add_trace() instead of leaving these args as None. Thank you again!!!