Synchronize camera across 3D subplots?

I have a case where I have several 3D scatterplots side-by-side. All of them show the same data with different colorings and overlays. I would like to synchronize the cameras in the subplots so that they always show the same view. In other words, zooming/panning/rotating in one subplot will affect all of the 3D subplots. It appears that itā€™s possible to do something similar for 2D plots via the ā€œmatchesā€ axis attribute, but I am unsure about a 3D equivalent. Is this possible to do with the Python api?

1 Like

Hi @zotgabe,

is this what you are aiming to do? ->:

  • with ipywidgets :
import plotly.graph_objs as go
import pandas as pd
import ipywidgets as widgets

# Read data from a csv
z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv')

data1 = [
    go.Surface(
        z=z_data.values
    )
]

data2 = [
    go.Surface(
        z=z_data.values, colorscale='viridis'
    )
]
layout1 = go.Layout(title=dict(text='Mt Bruno Elevation'))
layout2 = go.Layout(title=dict(text='Mt Bruno Elevation 2'))

fig1 = go.FigureWidget(data=data1, layout=layout1)
fig2 = go.FigureWidget(data=data2, layout=layout2)

def cam_change(layout, camera):
    fig2.layout.scene.camera = camera

fig1.layout.scene.on_change(cam_change, 'camera')
display(widgets.HBox([fig1,fig2]))
  • or with subplots:
import plotly.graph_objs as go
import pandas as pd
from plotly import tools

# Read data from a csv
z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv')

trace1 = dict(type='surface', scene='scene1', z=z_data.values)

trace2 = dict(type='surface', scene='scene2', z=z_data.values, colorscale='viridis')

f= tools.make_subplots(rows=1, cols=2, specs=[[{'is_3d': True}, {'is_3d': True}]])

f.append_trace(trace1, 1, 1)
f.append_trace(trace2, 1, 2)

fig = go.FigureWidget(f)

def cam_change(layout, camera):
    fig.layout.scene2.camera = camera

fig.layout.scene1.on_change(cam_change, 'camera')
fig

3 Likes

Thank you for the help! Your example does exactly what I was looking for. Iā€™ve been using iplot exclusively and wasnā€™t familiar with the FigureWidget interface (still new to Plotly), it does seem very useful.

Unfortunately, my plot also uses animation frames, which seem to be not supported with FigureWidgets. From other forum posts, it sounds like thatā€™s a planned feature, so I guess Iā€™ll just have to wait for that to be implemented.

Edit: Actually it seems that I could probably accomplish what Iā€™m looking for via FigureWidget updates, so I guess this is completely solved.

Hi there,

I just wrote a solution to get around this by using a bit of javascript. This time it works by using only ā€˜go.Figureā€™ object and making the callback function via javascript and not the python backend.

import numpy as np
from IPython.core.display import display, HTML
from plotly.offline import plot
from plotly import tools
import plotly.graph_objs as go

# Read data from a csv
z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv')

trace1 = go.Surface(scene='scene1', z=z_data.values, colorscale='Blues')

trace2 = go.Surface(scene='scene2', z=z_data.values, colorscale='Greens')

f= tools.make_subplots(rows=1, cols=2, specs=[[{'is_3d': True}, {'is_3d': True}]])

f.append_trace(trace1, 1, 1)
f.append_trace(trace2, 1, 2)

fig = go.Figure(f)

# get the a div
div = plot(fig, include_plotlyjs=False, output_type='div')
# retrieve the div id (you probably want to do something smarter here with beautifulsoup)
div_id = div.split('=')[1].split()[0].replace("'", "").replace('"', '')
# your custom JS code
js = '''
    <script>
    var gd = document.getElementById('{div_id}');
    var isUnderRelayout = false

    gd.on('plotly_relayout', () => {{
      console.log('relayout', isUnderRelayout)
      if (!isUnderRelayout) {{
        Plotly.relayout(gd, 'scene2.camera', gd.layout.scene.camera)
          .then(() => {{ isUnderRelayout = false }}  )
      }}

      isUnderRelayout = true;
    }})
    </script>'''.format(div_id=div_id)
# merge everything
div = '<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>' + div + js
# show the plot 
display(HTML(div))

3 Likes

You are awesome! That worked beautifully with my plot, animations and all. Thanks so much for the help!

Definitely glad I decided to try out Plotly, itā€™s been perfect for my needs so far and this community seems great.

Can this be done with dash? I am trying to do it with no luck.

Hey Alexboiboi,

Nice work. Best solution I found so farā€¦
BUT :slight_smile:

Is there a possibility to even synchronize them both?
In this example only the second one is adapting.

And in the IPython widget, the second plot is jumping in an other position from time to timeā€¦

Thanks in advance
Ben

1 Like

Is it also possible to keep acquire this synchronization when the figure is saved as .html?

Awesome, that snippet still worked in the latest plotly

@pcicales from the futureā€¦ did you end up having any luck synchronizing 3D subplots? Iā€™m trying to find a solution using callbacks.

Thanks!
Matt

I have just put together a multiple page multiple tab multiple window synchronizing demo. Bit more complex than you might expect, but it works for me. The only think I have to do is refresh the window where I want to pick up the updated graph and click data. peteasa / pyquickly Ā· GitLab

1 Like

Is it possible to rotate them all together, online, meaning as a user viewing the chart, using the rotation widgets present in the html chart?