Why can't I align my y axes in my plotly.js scatter chart like in this code pen example?

83 views Asked by At

We are developing an AngularJS application with plotly.js charts. We have a scatter plot that can have multiple y axes. We would like to stack the y axes horizontally to the right of the chart (ideally) or every odd y axes on the left and every even y axes on the right. There is an example of how this can be done in the Plotly.js references docs here with a link to a code pen example. Notice how the chart has 2 y axes stacked on the left side and 2 y axes stacked on the right side:

enter image description here

But when I attempt to mimic this behavior, I get this:

enter image description here

Note that the kWh axis (blue) is positioned within the chart (about 85% in from the left). Why does it do this whereas the code pen example does not?

Here is my trace data:

[
   {
      "type":"scatter",
      "x":[...],
      "y":[...],
      "hovertemplate":"%{x}<br>%{y:.2f} %{text}<extra></extra>",
      "text":[...],
      "name":"°C",
      "marker":{
         "color":"#89d300",
         "size":1,
         "opacity":1
      },
      "line":{
         "width":1
      },
      "yaxis":"y",
      "connectgaps":true,
      "measurementGroupId":18
   },
   {
      "type":"scatter",
      "x":[...],
      "y":[...],
      "hovertemplate":"%{x}<br>%{y:.2f} %{text}<extra></extra>",
      "text":[...],
      "name":"m3",
      "marker":{
         "color":"#3c3c3c",
         "size":1,
         "opacity":1
      },
      "line":{
         "width":1
      },
      "yaxis":"y2",
      "connectgaps":true,
      "measurementGroupId":8
   },
   {
      "type":"scatter",
      "x":[...],
      "y":[...],
      "hovertemplate":"%{x}<br>%{y:.2f} %{text}<extra></extra>",
      "text":[...],
      "name":"kWh",
      "marker":{
         "color":"#2fb5ea",
         "size":1,
         "opacity":1
      },
      "line":{
         "width":1
      },
      "yaxis":"y3",
      "connectgaps":true,
      "measurementGroupId":2
   }
]

And here is my layout:

{
   "title":{
      "text":"Time Series",
      "font":{
         "color":"#3dcd58",
         "family":"Nunito-Regular",
         "size":18
      },
      "xanchor":"left",
      "x":0
   },
   "font":{
      "color":"#333",
      "family":"Nunito-Regular"
   },
   "dragmode":"select",
   "autosize":true,
   "margin":{
      "l":20,
      "r":15,
      "b":30,
      "t":30
   },
   "plot_bgcolor":"#ffffff",
   "showlegend":true,
   "legend":{
      "x":0,
      "bgcolor":"rgba(255,255,255,0.3)"
   },
   "xaxis":{
      "range":[
         1664845980000,
         1699440540000
      ]
   },
   "hovermode":"closest",
   "yaxis":{
      "showticklabels":true,
      "side":"left",
      "tickfont":{
         "color":"#89d300"
      },
      "range":[
         2.133101851851852,
         31.59375
      ],
      "name":"°C"
   },
   "yaxis2":{
      "showticklabels":true,
      "side":"right",
      "overlaying":"y",
      "tickfont":{
         "color":"#3c3c3c"
      },
      "range":[
         -1328.6000000000001,
         27900.6
      ],
      "name":"m3"
   },
   "yaxis3":{
      "showticklabels":true,
      "side":"left",
      "position":0.85,
      "overlaying":"y",
      "tickfont":{
         "color":"#2fb5ea"
      },
      "range":[
         -948.1500000000001,
         41163.15
      ],
      "name":"kWh"
   }
}

The code to build the layout looks like this:

var layout = {
    title: {
        text: 'Time Series',
        font: {
            color: "#3dcd58",
            family: "Nunito-Regular",
            size: 18
        },
        xanchor: "left",
        x: 0
    },
    font: {
        color: "#333",
        family: "Nunito-Regular"
    },
    dragmode: 'select',
    autosize: true,
    margin: {
        l: 20,
        r: 15,
        b: 30,
        t: 30
    },
    plot_bgcolor: '#ffffff',
    showlegend: true,
    legend: {
        x: 0,
        bgcolor: 'rgba(255,255,255,0.3)'
    },
    xaxis: {
        range: [minMaxDateValues[0], minMaxDateValues[1]]
    },
    hovermode: 'closest'
};
                        
...

for (var i = 0; i < visibleDrivers.length; i++) {
    var driver = visibleDrivers[i];
    var yAxisName = 'yaxis' + (i > 0 ? (i + 1) : '');
    var minMax = _.find(minMaxDriverValues, function(values) {
        return values.id === driver.nodeId + '.' + driver.measurementGroupId; 
    });
    if (!minMax) {
        minMaxDriverValues.push({
            id: driver.nodeId + '.' + driver.measurementGroupId,
            yAxis: yAxisName,
            minValue: driver.minValue,
            maxValue: driver.maxValue
        });
    }
    layout[yAxisName] = {
        showticklabels: true,
        side: i % 2 > 0 ? 'right' : 'left',
        position: i < 2 ? undefined : 0.85,
        overlaying: i > 0 ? 'y' : undefined,
        tickfont: { color: driver.color },
        range: driver.measurementGroupId === 2
            ? energyRange
            : [
                minMax ? minMax.minValue : driver.minValue,
                minMax ? minMax.maxValue : driver.maxValue
            ],
        name: driver.unit
    }
}

Note that I am toggling the side the y axis shows up on like this: side: i % 2 > 0 ? 'right' : 'left'. And I am setting the position of the y axis like this: position: i < 2 ? undefined : 0.85.

This works well for the first two y axes. The first gets placed on the left with no position, and the second gets placed on the right with no position. Then when it comes to the third y axis, it gets assigned to the right and gets a position of .85. This apparently (unlike the code pen example) positions it 85% into the chart from the left (even though it's assigned to the right side).

I can try a hack of setting it's position to 1.0, which places it as far right on the chart as it can go (but still inside the chart):

enter image description here

But this is not ideal because 1) we want the axes to sit outside the chart, and 2) what happens when we have a fourth or fifth or sixth y axis? Anything beyond a position of 1.0 seems to just be ignored and positions the axis back at the beginning (left side of the chart):

enter image description here

Looking at my code and the resultant layout, can anyone see what I'm doing wrong that the code pen example is doing right? Thanks!

2

There are 2 answers

1
EricLavault On BEST ANSWER

You need to constrain the xaxis domain to make some room for the y axes, eg.

const layout = {
  // ...
  xaxis: { domain: [0.0, 0.9] }
}

This makes the portion of the plot [0.9, 1.0] available, also now the y axes set with side: 'right' are positioned at 0.9 by default, so you would then adjust the position of the third y axis, eg.

position: i < 2 ? undefined : 0.95,
2
opike On

What happens if you have an explicit width like this:

   "width":800,
   "title":{
      "text":"Time Series",
      "font":{
         "color":"#3dcd58",
         "family":"Nunito-Regular",
         "size":18
      },
      "xanchor":"left",
      "x":0
   },
   "font":{
      "color":"#333",
      "family":"Nunito-Regular"
   },
   Rest snipped...