Python Plotly figure with secondary x axis linked to primary

Viewed 2053

I've been struggling with this seemingly simple task: How to align two x axis with related data. In my case one axis is in Celsius and the other in Fahrenheit.

What I want to achieve is to obtain alignment of the two x axis so that:

32°F = 0°C And 50°F = 10°C

With this relation, the two datasets will be aligned in terms of temperature.

I want to have both unit sets on the same graph so that the viewer can interpret the data according to the units they are used to.

Here is my code:

import plotly.graph_objects as go
from plotly.subplots import make_subplots
from plotly.graph_objs.layout import YAxis,XAxis,Margin

layout = go.Layout(
    title="Double X Axis Example",
    xaxis=XAxis(
        title="Celcius"
    ),
    xaxis2 = XAxis(
        title="Fahrenheits",
        overlaying= 'x', 
        side= 'top',
    ),
    yaxis=dict(
        title="Y values"
    ),
)

# Create figure with secondary x-axis
fig = go.Figure(layout=layout)

# Add traces
fig.add_trace(
    go.Scatter(x=[10, 20, 30], y=[4.5, 6, 5], name="data set in celcius"),
)

fig.add_trace(
    go.Scatter(x=[40, 60, 80], y=[4, 5, 6.5], name="data set in fahrenheit", xaxis='x2'),
)

fig.show()

Here is the resulting figure with the unaligned axes (10°C = 40°F !?): Resulting figure

Thank you,

2 Answers

In this case it might help to set the ranges for the x-axes, something like this:

fig.add_trace(
    go.Scatter(x=[10, 20, 30], y=[4.5, 6, 5,], name="data set in celcius",xaxis="x1"),
)

fig.add_trace(
    go.Scatter(x=[40, 60, 80], y=[4, 5, 6.5], name="data set in fahrenheit", xaxis='x2'),
)


fig.update_layout(
    xaxis1=dict(range=[0, 100]),
    xaxis2=dict(range=[32, 212]),
    )

...possibly calculating the limit needed of x1 and then base x2 limit on that.

This is my solution and code to your concern. Here, I set the range of the first and second x axes to [0, 100] and [32, 212], respectively. To align the two axes, I made 26 tick marks for both axes and they are aligned because of the equal number of tick marks. Having an equal number of tick marks for both axes (and equal ranges) is crucial so that the aligned numbers are actually equal. Assuming that most data sets that will be plotted are between 0 and 100 degrees Celsius (for data in Celsius) --- or 32 and 212 degrees Fahrenheit (for data in Fahrenheit) --- I believe this solution overflows the data and the traces won't cover the full x ranges. Plot of the graph here.

import numpy as np
import plotly.graph_objects as go
arr1 = np.array([10, 20, 30])
arr2 = np.array([4.5,  6, 5])

arr3 = np.array([40, 60, 80])
arr4 = np.array([4,  5,  6.5])

fig = go.Figure(go.Scatter( x=arr1, y=arr2, name='data set in celsius' ) )
fig.add_trace(go.Scatter( x=arr3, y=arr4, xaxis='x2', name='data set in fahrenheit' ))
fig.update_layout(title_text='Double X Axis Example',
                  legend=dict(yanchor='top', y=0.875, xanchor='right', x=1),
                  yaxis=dict(domain = [0.05, 0.875], title='Y values', spikemode='toaxis', spikesnap='cursor'), template='plotly_dark', 
                  xaxis =dict(position = 0, title='Celsius', spikemode='across', spikesnap='cursor', 
                              tickmode='array', tickvals=np.linspace(0,100,26), range=[0,100]),
                  xaxis2=dict(position = 0.9, title='Fahrenheit', anchor='free', overlaying='x', side='top', tickmode='array', 
                              tickvals=np.linspace(32,212,26), range=[32,212], spikemode='across', spikesnap='cursor' ) 
                 )
fig.show()
Related