简体   繁体   English

如何使用 Plotly.js 同步多个图表的缩放级别?

[英]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.我使用 Plotly.js 创建了 3 个时间序列图,并且我希望保持缩放级别/平移同步,因此如果用户缩放/平移 3 个图中的任何一个,则其他 2 个得到更新。

Right now I have an event handler set on the first chart for the plotly_relayout event.现在我在第一个图表上为plotly_relayout事件设置了一个事件处理程序。 The event handler uses the event data passed in for calling Plotly.relayout on the other two charts.事件处理程序使用传入的事件数据调用其他两个图表上的Plotly.relayout

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?有谁知道如何确定哪个图首先发出了plotly_relayout事件,这样它就不会陷入处理自己事件的循环中? 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 . 见笔在Plotly.js图表链接放大活动由不宁头脑工作室( @restlessmindsstudio上) CodePen

As Ashish Rathi noticed, the above solution has a bug - code execution enters infinite loop when you try to push certain buttons.正如 Ashish Rathi 所注意到的,上述解决方案有一个错误 - 当您尝试按下某些按钮时,代码执行会进入无限循环。 Solution for that would be to check 'ed' variable in that own relayout handler and if it has no attributes - just to exit.解决方案是在自己的重新布局处理程序中检查 'ed' 变量,如果它没有属性 - 只是退出。 Looks like in some cases Plotly sends that plotly_relayout event with empty object containing updates just for fun - probably some kind of bug.看起来在某些情况下 Plotly 发送带有包含更新的空对象的 plotly_relayout 事件只是为了好玩 - 可能是某种错误。

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.在我的情况下,我不想让 Y 缩放从一个图传播到另一个图 - 因此我准备了一个单独的结构,其中仅包含我想要传播的那些属性。

 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>

I'm duplicating @ChrisG's comment here to give him credit and show my question as answered:我在此处复制@ChrisG 的评论,以表扬他并回答我的问题:

Found a much better solution: https://codepen.io/anon/pen/NXRpzW?editors=1010找到了一个更好的解决方案: 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>

Working from both @RMS (@ChrisG's) and @Alex's answers, I still had problems when I wanted to zoom both x and y (the browser would freeze completely either while zooming in one of the directions or when zooming out).根据@RMS(@ChrisG 的)和@Alex 的答案工作,当我想同时缩放 x 和 y 时仍然遇到问题(浏览器在放大方向之一或缩小时会完全冻结)。 I have just started using js for this function, but I made it work for any kind of zoom this way:我刚刚开始使用 js 来实现这个功能,但我通过这种方式使它适用于任何类型的缩放:

 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: { 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: { 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: { 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; let y = div.layout.yaxis; var update = {}; if ("xaxis.autorange" in ed && ed["xaxis.autorange"] != x.autorange) { update['xaxis.autorange']= ed["xaxis.autorange"]; } if ("yaxis.autorange" in ed && ed["yaxis.autorange"] != y.autorange) { update['yaxis.autorange'] = ed["yaxis.autorange"]; } if ("xaxis.range[0]" in ed && ed["xaxis.range[0]"] != x.range[0]) { update['xaxis.range[0]'] = ed["xaxis.range[0]"]; } if ("xaxis.range[1]" in ed && ed["xaxis.range[1]"] != x.range[1]) { update['xaxis.range[1]'] = ed["xaxis.range[1]"]; } if ("yaxis.range[0]" in ed && ed["yaxis.range[0]"] != y.range[0]) { update['yaxis.range[0]'] = ed["yaxis.range[0]"]; } if ("yaxis.range[1]" in ed && ed["yaxis.range[1]"] != y.range[1]) { update['yaxis.range[1]'] = ed["yaxis.range[1]"]; } Plotly.relayout(div, update); }); } divs.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>

Here is also a codepen link .这里还有一个 codepen 链接

Similar thing can be done in plotly dash.类似的事情可以在 plotly dash 中完成。 Code snippet is below.代码片段如下。

app=dash.Dash()

app.layout = html.Div([
            dcc.Graph(id='graph',figure=fig),
            html.Pre(id='relayout-data', style=styles['pre']),
            dcc.Graph(id='graph2', figure=fig)])

@app.callback(Output('relayout-data', 'children'),
              [Input('graph', 'relayoutData')])
def display_relayout_data(relayoutData):
    return json.dumps(relayoutData, indent=2)


@app.callback(Output('graph2', 'figure'),
             [Input('graph', 'relayoutData')], 
             [State('graph2', 'figure')])
def graph_event(select_data,  fig):
    try:
       fig['layout'] = {'xaxis':{'range':[select_data['xaxis.range[0]'],select_data['xaxis.range[1]']]}}
    except KeyError:
       fig['layout'] = {'xaxis':{'range':[zoomed out value]}}
return fig

app.run_server()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM