Align Multiple Y axis to one value in Plotly

Viewed 4285

I have the following code (slightly modified from Plotly's page)

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

# Create figure with secondary y-axis
fig = make_subplots(specs=[[{"secondary_y": True}]])

# Add traces
fig.add_trace(
    go.Scatter(x=[1, 2, 3], y=[40, 50, 60], name="yaxis data"),
    secondary_y=False,
)

fig.add_trace(
    go.Scatter(x=[2, 3, 4], y=[80, 40, 30], name="yaxis2 data"),
    secondary_y=True,
)

# Add figure title
fig.update_layout(
    title_text="Double Y Axis Example"
)

# Set x-axis title
fig.update_xaxes(title_text="xaxis title")

# Set y-axes titles
fig.update_yaxes(title_text="<b>primary</b> yaxis title", secondary_y=False)
fig.update_yaxes(title_text="<b>secondary</b> yaxis title", secondary_y=True)

fig.show()

This gives as a result

enter image description here

Now you see the two red circles on on the left and one on the right. You can see that the value 50 is not aligned to the same rect.

How can I make that the left Y axis and the right Y axis are aligned at one particular point? (in the majority of cases it would be 0)

EDIT: I would like to clarify that the values in both axis (left and right) can be widely different. Like

enter image description here

I just want the alignment of one value (in this case the 0) to be at the same level

2 Answers

You can do it like this:

  1. Set (or find) appropriate minimum and maximum values for your y-axes,
  2. adjust your y ranges using fig.update_layout(yaxis=dict(range=[all_min,all_max]), yaxis2=dict(range=[all_min,all_max])), and
  3. set the scaleanchor of your secondary y-axis to y1 like this: fig.update_layout(yaxis2=dict(scaleanchor = 'y1'))

Plot:

enter image description here

If the soruce of your figure data is a pandas dataframe, there are more elegant ways of finding global max and min values than just hard-coding them in there. Otherwise, the approach will be the same.

Complete code:

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

# Create figure with secondary y-axis
fig = make_subplots(specs=[[{"secondary_y": True}]])

# Add traces
fig.add_trace(
    go.Scatter(x=[1, 2, 3], y=[40, 50, 60], name="yaxis data"),
    secondary_y=False,
)

fig.add_trace(
    go.Scatter(x=[2, 3, 4], y=[80, 40, 30], name="yaxis2 data"),
    secondary_y=True,
)

# Add figure title
fig.update_layout(
    title_text="Double Y Axis Example"
)

# Set x-axis title
fig.update_xaxes(title_text="xaxis title")

# Set y-axes titles
fig.update_yaxes(title_text="<b>primary</b> yaxis title", secondary_y=False)
fig.update_yaxes(title_text="<b>secondary</b> yaxis title", secondary_y=True)

all_min = 10
all_max = 100
fig.update_layout(yaxis=dict(range=[all_min,all_max]), yaxis2=dict(range=[all_min,all_max]))
# fig.update_layout(yaxis2=dict(scaleanchor = 50))

fig.show()

I have similar objective but using plotly.js rather than python. But it should be possible to translate the calculation of range to python.

This still needs some margin added but otherwise seems about right:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset ="utf-8">
    <title>test plotly</title>
    <script src="https://cdn.plot.ly/plotly-2.4.2.min.js"></script>
  </head>
  <body>
    <div id="tester" style="width: 100%"></div>
    <script>
      let data = [
              {
                      x: [1, 2, 3],
                      y: [40, 50, 60],
                      mode: 'lines+markers',
                      name: 'yaxis data'
                    },
              {
                      x: [2, 3, 4],
                      y: [80, 40, 30],
                      mode: 'lines+markers',
                      name: 'yaxis2 data',
                      yaxis: 'y2'
                    }
            ];

      let align = 50;

      // get min and max of each data set
      let y1min = Math.min(align, ...data[0].y);
      let y1max = Math.max(align, ...data[0].y);
      let y2min = Math.min(align, ...data[1].y);
      let y2max = Math.max(align, ...data[1].y);

      let r1 = y1max - y1min;
      let r2 = y2max - y2min;

      let y1mint = y1min - align;
      let y1maxt = y1max - align;
      let y2mint = y2min - align;
      let y2maxt = y2max - align;

      let scaley2 = [];

      if (y1mint < 0 && y2mint < 0) {
              const scale = y1mint / y2mint;
              scaley2.push(scale);
            }

      if (y1maxt > 0 && y2maxt > 0) {
              const scale = y1maxt / y2maxt;
              scaley2.push(scale);
            }

      let scale = 1;
      if (scaley2.length === 2) {
          scale = (scaley2[0] + scaley2[1]) / 2;
      } else if (scaley2.length === 1) {
          scale = scaley2[0];
      }

      let r1min = align + Math.min(y1mint, y2mint * scale);
      let r1max = align + Math.max(y1maxt, y2maxt * scale);
      let r2min = align + Math.min(y2mint, y1mint / scale);
      let r2max = align + Math.max(y2maxt, y1maxt / scale);

      TESTER = document.getElementById('tester');
      Plotly.newPlot( TESTER,
              data,
              {
                      margin: {t: 0 },
                      width: 700,
                      xaxis: { title: 'day#' },
                      yaxis: {
                              title: '<b>primary</b> yaxis title',
                              range: [ r1min, r1max ]
                            },
                      yaxis2: {
                              title: '<b>secondary</b> yaxis title',
                              overlaying: 'y',
                              side: 'right',
                              range: [ r2min, r2max ]
                            },
                      legend: {
                              x: 1.13,
                              y: 0.5
                            }
                    }
            );
    </script>
  </body>
</html>

Set align to the y axis value you want aligned.

Related