I am working with dash_vtk to render a 3D scene. I want to display a small XYZ coordinate axis (like an orientation marker in VTK or similar to Plotly 3D axes). Like shown in this demo picture
However, I couldnât find a built-in way to add this in dash_vtk.
How i can go further and implement such axis?
ââââCode Snippet âââ
import dash
from dash import dcc, html, Input, Output, State
import plotly.graph_objects as go
app = dash.Dash(__name__)
def create_figure():
fig = go.Figure()
# Main geometry: cube
fig.add_trace(go.Mesh3d(
x=[0,1,1,0,0,1,1,0],
y=[0,0,1,1,0,0,1,1],
z=[0,0,0,0,1,1,1,1],
color="lightgray",
opacity=1
))
# Corner axes
axes = [
([0,1],[0,0],[0,0],"red","X"),
([0,0],[0,1],[0,0],"green","Y"),
([0,0],[0,0],[0,1],"blue","Z")
]
for x,y,z,c,label in axes:
fig.add_trace(go.Scatter3d(
x=x, y=y, z=z,
mode="lines+text",
line=dict(color=c,width=4),
text=["",label],
textposition="top center",
scene="scene2",
showlegend=False
))
return fig
"""
fig.update_layout(
margin=dict(l=0,r=0,t=0,b=0),
scene=dict(
aspectmode="cube",
xaxis=dict(visible=False, range=[-0.5,1.5]),
yaxis=dict(visible=False, range=[-0.5,1.5]),
zaxis=dict(visible=False, range=[-0.5,1.5]),
),
scene2=dict(
domain=dict(x=[0.02,0.18], y=[0.02,0.18]),
aspectmode="cube",
xaxis=dict(visible=False),
yaxis=dict(visible=False),
zaxis=dict(visible=False),
bgcolor="rgba(0,0,0,0)",
camera=dict(eye=dict(x=1.8,y=1.8,z=1.8))
),
uirevision="locked"
)
return fig
"""
fig = create_figure()
app.layout = html.Div([
dcc.Graph(id="viewer", figure=fig, style={"height":"600px","width":"600px","bg-color":"black"}),
# Hidden div to store last updated scene id to avoid infinite loop
html.Div(id="last-updated-scene", style={"display":"none"})
])
@app.callback(
Output("viewer", "figure"),
Output("last-updated-scene", "children"),
Input("viewer", "relayoutData"),
State("viewer", "figure"),
State("last-updated-scene", "children"),
prevent_initial_call=True
)
def two_way_sync(relayout, fig_state, last_updated):
if not relayout:
return fig_state, last_updated
# Determine which camera moved: 'scene.camera' or 'scene2.camera'
moved_scene = None
if "scene.camera" in relayout:
moved_scene = "scene"
elif "scene2.camera" in relayout:
moved_scene = "scene2"
# If no camera moved or same scene triggered last update, do nothing
if moved_scene is None or moved_scene == last_updated:
return fig_state, last_updated
cam = relayout.get(f"{moved_scene}.camera")
if not cam:
return fig_state, last_updated
# Calculate scaled eye for scene2 to keep separation
eye = cam.get("eye")
mag = (eye["x"]**2 + eye["y"]**2 + eye["z"]**2)**0.5 if eye else 1
# Copy camera to the *other* scene
target_scene = "scene2" if moved_scene == "scene" else "scene"
new_eye = {
"x": eye["x"]/mag*1.8 if eye else 1.8,
"y": eye["y"]/mag*1.8 if eye else 1.8,
"z": eye["z"]/mag*1.8 if eye else 1.8,
}
# Update the other scene's camera
fig_state["layout"][f"{target_scene}_camera"] = dict(
eye=new_eye,
center=cam.get("center", {"x":0,"y":0,"z":0}),
up=cam.get("up", {"x":0,"y":0,"z":1})
)
return fig_state, moved_scene
if __name__ == "__main__":
app.run(debug=True)
