Skip to content

Commit bd6b01d

Browse files
feat: Add mesh hiding dropdown
1 parent 1f8a63b commit bd6b01d

File tree

3 files changed

+286
-4
lines changed

3 files changed

+286
-4
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright (C) 2024 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
"""
23+
.. _ref_plain_usage_dash:
24+
25+
======================================
26+
Plain usage of the plotly dash backend
27+
======================================
28+
29+
This example shows the plain usage of the Plotly Dash backend in the Visualization Interface Tool to plot different objects,
30+
including PyVista meshes, custom objects, and Plotly-specific objects.
31+
"""
32+
33+
from ansys.tools.visualization_interface.backends.plotly.plotly_dash import PlotlyDashBackend
34+
from ansys.tools.visualization_interface.types.mesh_object_plot import MeshObjectPlot
35+
from ansys.tools.visualization_interface import Plotter
36+
import pyvista as pv
37+
from plotly.graph_objects import Mesh3d
38+
39+
40+
# Create a plotter with the Plotly backend
41+
pl = Plotter(backend=PlotlyDashBackend())
42+
43+
# Create a PyVista mesh
44+
mesh = pv.Sphere()
45+
mesh2 = pv.Cube(center=(2,0,0))
46+
# Plot the mesh
47+
pl.plot(mesh, name="Sphere")
48+
pl.plot(mesh2, name="Cube")
49+
50+
# ----------------------------------
51+
# Start the server and show the plot
52+
# ----------------------------------
53+
#
54+
# .. code-block:: python
55+
#
56+
# pl.show()
57+

src/ansys/tools/visualization_interface/backends/plotly/plotly_dash.py

Lines changed: 140 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
"""Module for dash plotly."""
2323
from typing import TYPE_CHECKING, Union
2424

25-
from dash import Dash, dcc, html
25+
from dash import Dash, Input, Output, dcc, html
2626

2727
from ansys.tools.visualization_interface.backends.plotly.plotly_interface import PlotlyBackend
28+
from ansys.tools.visualization_interface.backends.plotly.widgets.dropdown_manager import DashDropdownManager
2829

2930
if TYPE_CHECKING:
3031
import plotly.graph_objects as go
@@ -43,6 +44,142 @@ def __init__(self, app: Dash = None) -> None:
4344
"""
4445
super().__init__()
4546
self._app = app or Dash(__name__)
47+
self._dropdown_manager = DashDropdownManager(self._fig)
48+
self._setup_callbacks()
49+
50+
@property
51+
def dropdown_manager(self) -> DashDropdownManager:
52+
"""Get the dropdown manager for this backend.
53+
54+
Returns
55+
-------
56+
DashDropdownManager
57+
The dropdown manager instance.
58+
"""
59+
return self._dropdown_manager
60+
61+
def plot(self, plottable_object, name: str = None, **plotting_options) -> None:
62+
"""Plot a single object using Plotly and track mesh names for dropdown.
63+
64+
Parameters
65+
----------
66+
plottable_object : Any
67+
The object to plot.
68+
name : str, optional
69+
Name of the mesh for labeling in Plotly.
70+
plotting_options : dict
71+
Additional plotting options.
72+
"""
73+
# Call parent plot method
74+
super().plot(plottable_object, name=name, **plotting_options)
75+
76+
# Track mesh names for dropdown functionality
77+
if name:
78+
self._dropdown_manager.add_mesh_name(name)
79+
else:
80+
# Try to get name from the latest trace added
81+
if self._fig.data:
82+
latest_trace = self._fig.data[-1]
83+
trace_name = getattr(latest_trace, 'name', None)
84+
if trace_name:
85+
self._dropdown_manager.add_mesh_name(trace_name)
86+
87+
def _setup_callbacks(self) -> None:
88+
"""Setup Dash callbacks for mesh visibility control."""
89+
# Store reference to self for use in callback
90+
backend_instance = self
91+
92+
@self._app.callback(
93+
Output('mesh-graph', 'figure'),
94+
Input('mesh-visibility-dropdown', 'value'),
95+
prevent_initial_call=True
96+
)
97+
def update_mesh_visibility(hidden_meshes):
98+
"""Update mesh visibility based on dropdown selection.
99+
100+
Parameters
101+
----------
102+
hidden_meshes : List[str]
103+
List of mesh names to hide.
104+
105+
Returns
106+
-------
107+
go.Figure
108+
Updated figure with modified mesh visibility.
109+
"""
110+
if hidden_meshes is None:
111+
hidden_meshes = []
112+
113+
# Get all mesh names
114+
all_mesh_names = backend_instance.dropdown_manager.get_mesh_names()
115+
visible_mesh_names = [name for name in all_mesh_names if name not in hidden_meshes]
116+
117+
# Create a copy of the figure to avoid modifying the original
118+
import plotly.graph_objects as go
119+
updated_fig = go.Figure(backend_instance._fig)
120+
121+
# Update visibility for each trace
122+
for i, trace in enumerate(updated_fig.data):
123+
trace_name = getattr(trace, 'name', None)
124+
is_visible = trace_name in visible_mesh_names
125+
updated_fig.data[i].visible = is_visible
126+
127+
return updated_fig
128+
129+
def create_dash_layout(self) -> html.Div:
130+
"""Create the Dash layout with optional dropdown for mesh visibility.
131+
132+
Returns
133+
-------
134+
html.Div
135+
The Dash layout component.
136+
"""
137+
components = []
138+
139+
if self.dropdown_manager.get_mesh_names():
140+
# Add dropdown for mesh visibility control
141+
mesh_names = self.dropdown_manager.get_mesh_names()
142+
components.append(
143+
dcc.Dropdown(
144+
id='mesh-visibility-dropdown',
145+
options=[{'label': name, 'value': name} for name in mesh_names],
146+
multi=True,
147+
placeholder="Select meshes to hide",
148+
searchable=True,
149+
style={
150+
'width': '280px',
151+
'fontSize': '14px'
152+
}
153+
)
154+
155+
)
156+
157+
# Add the main graph
158+
components.append(dcc.Graph(
159+
id='mesh-graph',
160+
figure=self._fig,
161+
style={
162+
'height': '100vh',
163+
'width': '100%',
164+
'margin': '0',
165+
'padding': '0'
166+
},
167+
config={
168+
'responsive': True,
169+
'displayModeBar': True,
170+
'displaylogo': False
171+
}
172+
))
173+
174+
return html.Div(components, style={
175+
'fontFamily': '"Open Sans", verdana, arial, sans-serif',
176+
'backgroundColor': '#ffffff',
177+
'minHeight': '100vh',
178+
'width': '100%',
179+
'margin': '0',
180+
'padding': '0',
181+
'position': 'relative'
182+
})
46183

47184
def show(self,
48185
plottable_object=None,
@@ -76,9 +213,8 @@ def show(self,
76213

77214
# Only show in browser if no screenshot is being taken
78215
if not screenshot:
79-
self._app.layout = html.Div([
80-
dcc.Graph(figure=self._fig)
81-
])
216+
# Always use the create_dash_layout method to ensure dropdown is included when enabled
217+
self._app.layout = self.create_dash_layout()
82218
self._app.run()
83219

84220
else:
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright (C) 2024 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
"""Module for dropdown management in Plotly figures."""
23+
from typing import Any, Dict, List
24+
25+
import plotly.graph_objects as go
26+
27+
28+
class DashDropdownManager:
29+
"""Class to manage dropdown menus in a Plotly figure.
30+
31+
This class allows adding dropdown menus to a Plotly figure for controlling
32+
mesh visibility and other properties.
33+
34+
Parameters
35+
----------
36+
fig : go.Figure
37+
The Plotly figure to which dropdowns will be added.
38+
"""
39+
40+
def __init__(self, fig: go.Figure):
41+
"""Initialize DropdownManager."""
42+
self._fig = fig
43+
self._mesh_names = []
44+
45+
def add_mesh_name(self, name: str) -> None:
46+
"""Add a mesh name to track for dropdown functionality.
47+
48+
Parameters
49+
----------
50+
name : str
51+
The name of the mesh to track.
52+
"""
53+
if name and name not in self._mesh_names:
54+
self._mesh_names.append(name)
55+
56+
def get_mesh_names(self) -> List[str]:
57+
"""Get the list of tracked mesh names.
58+
59+
Returns
60+
-------
61+
List[str]
62+
List of mesh names.
63+
"""
64+
return self._mesh_names.copy()
65+
66+
def get_visibility_args_for_meshes(self, visible_mesh_names: List[str]) -> Dict[str, Any]:
67+
"""Get visibility arguments for showing only specified meshes.
68+
69+
Parameters
70+
----------
71+
visible_mesh_names : List[str]
72+
List of mesh names that should be visible.
73+
74+
Returns
75+
-------
76+
Dict[str, Any]
77+
Arguments for restyle method to set mesh visibility.
78+
"""
79+
visibility = []
80+
for trace in self._fig.data:
81+
trace_name = getattr(trace, 'name', None)
82+
is_visible = trace_name in visible_mesh_names
83+
visibility.append(is_visible)
84+
85+
return {"visible": visibility}
86+
87+
def clear(self) -> None:
88+
"""Clear all tracked mesh names."""
89+
self._mesh_names.clear()

0 commit comments

Comments
 (0)