Synchronize camera across 3D subplots?

#1

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?

#2

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

2 Likes
#3

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.

#4

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))

2 Likes
#5

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.