Announcing Dash Bio 1.0.0 🎉 : a one-stop-shop for bioinformatics and drug development visualizations.

Filled area plots for lines not working if two of them are on different Y-axis

My intention is to color the spread between two line charts that have different Y-axis (one is log scale, one is nominal scale)

The filled area feature works s intended if both lines are in the same Y-axis.

import plotly.graph_objects as go

fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[3, 4, 8, 3],
    fill=None,
    mode='lines',
    line_color='indigo',
    ))
fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[1, 6, 2, 6],
    fill='tonexty', # fill area between trace0 and trace1
    mode='lines', line_color='indigo'))

fig.show()

Capture

However, it fails if both lines belonged to a different Y-axis

import plotly.graph_objects as go

fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[3, 4, 8, 3],
    fill=None,
    mode='lines',
    line_color='indigo',
    yaxis= 'y1',         
    ))

fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[1, 6, 2, 6],
    fill='tonexty', # fill area between trace0 and trace1
    mode='lines', line_color='indigo',
    yaxis= 'y2'
))

fig.update_layout(
    xaxis=dict(
       domain=[0.15, 0.9]
    ),
    yaxis=dict(
        title="Y1",
        titlefont=dict(
            color="#1f77b4"
        ),
        tickfont=dict(
            color="#1f77b4"
        )
    ),
    yaxis2=dict(
        title="Y2",
        titlefont=dict(
            color="red"
        ),
        tickfont=dict(
            color="red"
        ),
        anchor="x",
        overlaying="y",
        side="right",
   ),
)

fig.show()

Capture1

Is there a way I can fix this? (Or is there a feature that allows area filling for lines belonging to different Y-axis?)

@divadlen
The second plot does not reproduce the first one, because only graphs referenced to the same xaxis and yaxis can be compared.
But with a trick you can get it as follows:

Reference both graphs to yaxis, and a third one to the yaxis2. Set mode “markers” for the third trace, and set invisible markers (i.e. of small size). Also showlegend=False for the this one:

import plotly.graph_objects as go
from plotly.subplots import make_subplots
fig = make_subplots(specs=[[{"secondary_y": True}]])
y1 =  [3, 4, 8, 3]
y2= [1, 6, 2, 6]
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=y1,
    fill=None,
    mode='lines',
    line_color='indigo' ), secondary_y=False)
fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[1, 6, 2, 6],
    fill='tonexty', # fill area between trace0 and trace1
    mode='lines', line_color='indigo'), secondary_y=False)

fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[1, 6, 2, 6],
    showlegend=False,
    mode='markers', marker_size=0.1, line_color='indigo'), secondary_y=True)

fig.update_layout(
    xaxis_domain=[0.15, 0.9],
    yaxis=dict(
        title="Y1",
        titlefont_color="#1f77b4",
        tickfont_color="#1f77b4"
        ),
    yaxis2=dict(
        title="Y2",
        titlefont_color="red",
        tickfont_color="red"
        ))

fig.show()

Someone looking at this graph can wonder why are you comparing values of different ranges. You can get the right information setting the same range for both yaxis and yaxis2:

fig = make_subplots(specs=[[{"secondary_y": True}]])
y1 =   [3, 4, 8, 3]
y2 =  [1, 6, 2, 6]

ymin=min(y1+y2)
ymax=max(y1+y2)
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=y1,
    fill=None,
    mode='lines',
    line_color='indigo',
            
    ), secondary_y=False)
fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[1, 6, 2, 6],
    fill='tonexty', # fill area between trace0 and trace1
    mode='lines', line_color='indigo',
    
), secondary_y=False)


fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[1, 6, 2, 6],
    showlegend=False,
    mode='markers', marker_size=0.1, line_color='indigo',
    
), secondary_y=True)

fig.update_layout(
    xaxis_domain=[0.15, 0.9],
    yaxis=dict(
        title="Y1",
        range=[ymin-0.5, ymax+0.5],
        titlefont_color="#1f77b4",
        tickfont_color="#1f77b4"
        ),
    yaxis2=dict(
        title="Y2",
        range=[ymin-0.5, ymax+0.5],
        titlefont_color="red",
        tickfont_color="red"
        ))
1 Like

Someone looking at this graph can wonder why are you comparing values of different ranges. You can get the right information setting the same range for both yaxis and yaxis2:

My intention is to do cointegration analysis between time series, many of them often don’t come in the same scale (EG: labor hours, value-added, log-scaled, nominal scaled). So having a way to highlight the spread seems useful.

Unfortunately, your suggestion doesn’t seem to help the problem. As soon as I scale the numbers for a certain Ydata, all y-axis ends up scaling accordingly.

import plotly.graph_objects as go
from plotly.subplots import make_subplots
fig = make_subplots(specs=[[{"secondary_y": True}]])
y1 =  [3, 4, 8, 3]
y2= [1, 6, 2, 6]
fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=y1,
    fill=None,
    name= 'Y1 line',   
    mode='lines',
    line_color='blue' ), secondary_y=False)
fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[1, 6, 2, 6],
    name= 'Y2 line',
    fill='tonexty', # fill area between trace0 and trace1
    mode='lines', 
    line_color='red'), secondary_y=False)

fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[1, 6, 2, 6],
    showlegend=False,
    mode='markers', marker_size=0.1, 
    line_color='red'), secondary_y=True)

fig.update_layout(
    xaxis_domain=[0.15, 0.9],
    yaxis=dict(
        title="Y1",
        titlefont_color="#1f77b4",
        tickfont_color="#1f77b4"
        ),
    yaxis2=dict(
        title="Y2",
        titlefont_color="red",
        tickfont_color="red",
        ))

fig.show()

import plotly.graph_objects as go
from plotly.subplots import make_subplots
fig = make_subplots(specs=[[{"secondary_y": True}]])
y1 =  [3, 4, 8, 3]
y2= [1, 6, 2, 6]
fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=y1,
    fill=None,
    name= 'Y1 line',   
    mode='lines',
    line_color='blue' ), secondary_y=False)
fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[10, 60, 20, 60],
    name= 'Y2 line',
    fill='tonexty', # fill area between trace0 and trace1
    mode='lines', 
    line_color='red'), secondary_y=False)

fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[10, 60, 20, 60],
    showlegend=False,
    mode='markers', marker_size=0.1, 
    line_color='red'), secondary_y=True)

fig.update_layout(
    xaxis_domain=[0.15, 0.9],
    yaxis=dict(
        title="Y1",
        titlefont_color="#1f77b4",
        tickfont_color="#1f77b4"
        ),
    yaxis2=dict(
        title="Y2",
        titlefont_color="red",
        tickfont_color="red",
        ))

fig.show()

So Y1 in this case is plotted against the Y2-axis, bringing me back to my original problem. Ideally, Y1 axis should be 1-8, while Y2 axis should be 10-60. Changing the parameters of secondary_y= from False to True brings me back to my original problem.

The solution I’m trying to get is for Y1 and Y2 to be plot in independent-axis (which plotly already has a solution for), and to have the tonexty function to work for Y-lines that occur in different Y-axis scales.

Alternatively, I can just try to scale all different Ydata values to fit in the same Y-axis. Not sure if it would cause problems for research interpretation.

Original

import plotly.graph_objects as go

fig = go.Figure()

fig.add_trace(go.Scatter(
    x= df.Year,
    y= df['Value added'],
    name = 'Value added',
    yaxis= 'y1',
    mode= 'lines',
    fill= None,
))

fig.add_trace(go.Scatter(
    x= df.Year[1:],
    y= df['Wages paid'],
    name = 'Wages paid',
    yaxis= 'y2',
    mode= 'lines',
    fill= None,
))

fig.add_trace(go.Scatter(
    x= df.Year,
    y= df['Total workers by year end'],
    name = 'Total workers by year end',
    yaxis= 'y3',
    mode= 'lines',
    fill='tonexty', 
))



###############
fig.update_layout(
    xaxis=dict(
       domain=[0.15, 0.9]
    ),
    yaxis=dict(
        title="Value added ($)",
        titlefont=dict(
            color="#1f77b4"
        ),
        tickfont=dict(
            color="#1f77b4"
        )
    ),
    yaxis2=dict(
        title="Wages paid ($)",
        titlefont=dict(
            color="red"
        ),
        tickfont=dict(
            color="red"
        ),
        anchor="free",
        overlaying="y",
        side="left",
        position = 0.0
   ),
    yaxis3=dict(
        title="Total workers by year end",
        titlefont=dict(
            color="green"
        ),
        tickfont=dict(
            color="green"
        ),
        anchor="x",
        overlaying="y",
        side="right",
   ),    
    
    
    height= 600,
    width= 900,
    title= "Value Spread (1963-2019)"
)

fig.show()

Work around (using a rescaled data set, also the chart I wanted)

import plotly.graph_objects as go

fig = go.Figure()

fig.add_trace(go.Scatter(
    x= a.Year,
    y= a['Value added'],
    name = 'Value added',
    yaxis= 'y1',
    mode= 'lines',
    fill= None,
))

fig.add_trace(go.Scatter(
    x= a.Year,
    y= a['Wages paid'],
    name = 'Wages paid',
    yaxis= 'y1',
    mode= 'lines',
    fill= 'tonexty',
))

fig.add_trace(go.Scatter(
    x= a.Year,
    y= a['Total workers by year end'],
    name = 'Total workers by year end',
    yaxis= 'y1',
    mode= 'lines',
    fill='tonexty', 
))

###############
fig.update_layout(
    xaxis=dict(
       domain=[0.15, 0.9]
    ),
    yaxis=dict(
        title="Scaled values",
        titlefont=dict(
            color="#1f77b4"
        ),
        tickfont=dict(
            color="#1f77b4"
        )
    ),   
    
    height= 600,
    width= 900,
    title= "Value Spread, (1963-2019)"
)

fig.show()

However, this method loses the clarity at communicating the true scale for each variable. But thanks for the help and ideas anyway.