Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I synchronize the zoom level of multiple charts with Plotly.js?

I created 3 time-series plots using Plotly.js and I would like to keep the zoom level/panning synchronized so if the user zooms/pans on any of the 3 plots, the other 2 get updated.

Right now I have an event handler set on the first chart for the plotly_relayout event. The event handler uses the event data passed in for calling Plotly.relayout on the other two charts.

Does anyone know how to identify which plot first emitted the plotly_relayout event so it doesn't get stuck in a loop handling its own event? Or perhaps there's a better way to handle keeping the zoom/pan synchronized between multiple charts?

See the Pen Linked Zoom Events in Plotly.js Charts by Restless Minds Studio (@restlessmindsstudio) on CodePen.
like image 223
RMS Avatar asked Dec 21 '17 22:12

RMS


People also ask

How do you zoom in Plotly?

To zoom along only one axis, click and drag near the edges of either one of the axes. Additionally, to zoom-in along both the axes together, click and drag near the corners of both the axes.

Is Plotly responsive?

By default, figures you create with the plotly.py package are responsive. Responsive figures automatically change their height and width when the size of the window they are displayed in changes.

How do you turn off zoom in Plotly?

To disable zoom and panning you need to set: layout. xaxis. fixedrange = true and layout.

How do you share a Plotly interactive plot?

To share a plot from the Chart Studio Workspace, click 'Share' button on the left-hand side after saving the plot. The Share modal will pop-up and display a link under the 'Embed' tab. You can then copy and paste this link to your website. You have the option of embedding your plot as an HTML snippet or iframe.


2 Answers

As Ashish Rathi noticed, the above solution has a bug - code execution enters infinite loop when you try to push certain buttons. Solution for that would be to check 'ed' variable in that own relayout handler and if it has no attributes - just to exit. Looks like in some cases Plotly sends that plotly_relayout event with empty object containing updates just for fun - probably some kind of bug.

And in my case I didn't want to have Y zoom to be propagated from one plot to another - thus I prepared a separate structure with only those attributes I want to have propagated.

var myDiv = document.getElementById("myDiv");
var myDiv2 = document.getElementById("myDiv2");
var myDiv3 = document.getElementById("myDiv3");
var startTime = new Date().getTime();
var timeStep = 60000;
var myDivMax = 70;
var myDiv2Max = 9000;
var myDiv3Max = 500;

var d3 = Plotly.d3,
  N = 40,
  x = d3.range(N).map(() => {
    return new Date((startTime += timeStep));
  }),
  y = d3.range(N).map(() => Math.random() * myDivMax),
  data = [{ x: x, y: y }];
var layout = {
  height: 200,
  margin: { l: 45, t: 5, r: 45, b: 45 },
  xaxis: {
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  },
  yaxis: {
    fixedrange: true,
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  }
};
var layout2 = {
  height: 200,
  margin: { l: 45, t: 5, r: 45, b: 45 },
  xaxis: {
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  },
  yaxis: {
    fixedrange: true,
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  }
};
var layout3 = {
  height: 200,
  margin: { l: 45, t: 5, r: 45, b: 45 },
  xaxis: {
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  },
  yaxis: {
    fixedrange: true,
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  }
};

Plotly.plot(myDiv, data, layout);
var data2 = [{ x: x, y: d3.range(N).map(() => Math.random() * myDiv2Max) }];
Plotly.plot(myDiv2, data2, layout2);
var data3 = [{ x: x, y: d3.range(N).map(() => Math.random() * myDiv3Max) }];
Plotly.plot(myDiv3, data3, layout3);

var divs = [myDiv, myDiv2, myDiv3];

function relayout(ed, divs) {
  if (Object.entries(ed).length === 0) {return;}
  divs.forEach((div, i) => {
    let x = div.layout.xaxis;
    if (ed["xaxis.autorange"] && x.autorange) return;
    if (x.range[0] != ed["xaxis.range[0]"] ||x.range[1] != ed["xaxis.range[1]"])
    {
      var update = {
      'xaxis.range[0]': ed["xaxis.range[0]"],
      'xaxis.range[1]': ed["xaxis.range[1]"],
      'xaxis.autorange': ed["xaxis.autorange"],
     };
     Plotly.relayout(div, update);
    }
  });
}

var plots = [myDiv, myDiv2, myDiv3];
plots.forEach(div => {
  div.on("plotly_relayout", function(ed) {
    relayout(ed, divs);
  });
});
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<h2>Resize any chart, the others will get the same zoom level applied</h2>
<div id="myDiv"></div>
<div id="myDiv2"></div>
<div id="myDiv3"></div>
like image 91
Alex Avatar answered Sep 22 '22 02:09

Alex


I'm duplicating @ChrisG's comment here to give him credit and show my question as answered:

Found a much better solution: https://codepen.io/anon/pen/NXRpzW?editors=1010

var myDiv = document.getElementById("myDiv");
var myDiv2 = document.getElementById("myDiv2");
var myDiv3 = document.getElementById("myDiv3");
var startTime = new Date().getTime();
var timeStep = 60000;
var myDivMax = 70;
var myDiv2Max = 9000;
var myDiv3Max = 500;

var d3 = Plotly.d3,
  N = 40,
  x = d3.range(N).map(() => {
    return new Date((startTime += timeStep));
  }),
  y = d3.range(N).map(() => Math.random() * myDivMax),
  data = [{ x: x, y: y }];
var layout = {
  height: 200,
  margin: { l: 45, t: 5, r: 45, b: 45 },
  xaxis: {
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  },
  yaxis: {
    fixedrange: true,
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  }
};
var layout2 = {
  height: 200,
  margin: { l: 45, t: 5, r: 45, b: 45 },
  xaxis: {
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  },
  yaxis: {
    fixedrange: true,
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  }
};
var layout3 = {
  height: 200,
  margin: { l: 45, t: 5, r: 45, b: 45 },
  xaxis: {
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  },
  yaxis: {
    fixedrange: true,
    tickfont: {
      size: 10,
      color: "#7f7f7f"
    }
  }
};

Plotly.plot(myDiv, data, layout);
var data2 = [{ x: x, y: d3.range(N).map(() => Math.random() * myDiv2Max) }];
Plotly.plot(myDiv2, data2, layout2);
var data3 = [{ x: x, y: d3.range(N).map(() => Math.random() * myDiv3Max) }];
Plotly.plot(myDiv3, data3, layout3);

var divs = [myDiv, myDiv2, myDiv3];

function relayout(ed, divs) {
  divs.forEach((div, i) => {
    let x = div.layout.xaxis;
    if (ed["xaxis.autorange"] && x.autorange) return;
    if (
      x.range[0] != ed["xaxis.range[0]"] ||
      x.range[1] != ed["xaxis.range[1]"]
    )
      Plotly.relayout(div, ed);
  });
}

var plots = [myDiv, myDiv2, myDiv3];
plots.forEach(div => {
  div.on("plotly_relayout", function(ed) {
    console.log(ed);
    relayout(ed, divs);
  });
});
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<h2>Resize any chart, the others will get the same zoom level applied</h2>
<div id="myDiv"></div>
<div id="myDiv2"></div>
<div id="myDiv3"></div>
like image 32
RMS Avatar answered Sep 20 '22 02:09

RMS