Commit eb0d4a06 authored by Duggan, John's avatar Duggan, John
Browse files

Add Plotly walkthrough

parent 36b09116
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
"""Configuration for the PyVista example."""

from pydantic import BaseModel, Field
from pyvista import Plotter, examples

KNEE_DATA = examples.download_knee_full()


class PyVistaConfig(BaseModel):
    """Configuration class for the PyVista example."""

    colormap_options: list[str] = ["viridis", "autumn", "coolwarm", "twilight", "jet"]
    opacity_options: list[str] = ["linear", "sigmoid"]
    colormap: str = Field(default="viridis", title="Color Transfer Function")
    opacity: str = Field(default="linear", title="Opacity Transfer Function")

    def render(self, plotter: Plotter) -> None:
        # If re-rendering the volume on changes isn't acceptable, then you may need to switch to using VTK directly due
        # limitations of the PyVista volume rendering engine.
        plotter.clear()
        plotter.add_volume(KNEE_DATA, cmap=self.colormap, opacity=self.opacity, show_scalar_bar=False)

        plotter.render()
        plotter.view_isometric()
+7 −0
Original line number Diff line number Diff line
@@ -3,9 +3,11 @@
from typing import Any, Dict

from nova.mvvm.interface import BindingInterface
from pyvista import Plotter  # just for typing

from ..models.main_model import MainModel
from ..models.plotly import PlotlyConfig
from ..models.pyvista import PyVistaConfig


class MainViewModel:
@@ -14,11 +16,13 @@ class MainViewModel:
    def __init__(self, model: MainModel, binding: BindingInterface):
        self.model = model
        self.plotly_config = PlotlyConfig()
        self.pyvista_config = PyVistaConfig()

        self.plotly_config_bind = binding.new_bind(
            linked_object=self.plotly_config, callback_after_update=self.update_plotly_figure
        )
        self.plotly_figure_bind = binding.new_bind()
        self.pyvista_config_bind = binding.new_bind(linked_object=self.pyvista_config)

        # here we create a bind that connects ViewModel with View. It returns a communicator object,
        # that allows to update View from ViewModel (by calling update_view).
@@ -39,3 +43,6 @@ class MainViewModel:
    def update_plotly_figure(self, _: Any = None) -> None:
        self.plotly_config_bind.update_in_view(self.plotly_config)
        self.plotly_figure_bind.update_in_view(self.plotly_config.get_figure())

    def update_pyvista_volume(self, plotter: Plotter) -> None:
        self.pyvista_config.render(plotter)
+44 −0
Original line number Diff line number Diff line
"""View for the 3d plot using PyVista."""

from typing import Any, Optional

import pyvista as pv
from nova.trame.view.components import InputField
from nova.trame.view.layouts import GridLayout, HBoxLayout
from pyvista.trame.ui import plotter_ui
from trame.widgets import vuetify3 as vuetify

from ..view_models.main import MainViewModel


class PyVistaView:
    """View class for the 3d plot using PyVista."""

    def __init__(self, view_model: MainViewModel) -> None:
        self.view_model = view_model
        self.view_model.pyvista_config_bind.connect("pyvista_config")

        self.plotter: Optional[pv.Plotter] = None

        self.create_plotter()
        self.create_ui()

    def create_plotter(self) -> None:
        self.plotter = pv.Plotter(off_screen=True)

    def create_ui(self) -> None:
        vuetify.VCardTitle("PyVista")
        with GridLayout(columns=5, classes="mb-2", valign="center"):
            InputField(
                v_model="pyvista_config.colormap", column_span=2, items="pyvista_config.colormap_options", type="select"
            )
            InputField(
                v_model="pyvista_config.opacity", column_span=2, items="pyvista_config.opacity_options", type="select"
            )
            vuetify.VBtn("Render", click=self.update)
        with HBoxLayout(halign="center", height="50vh"):
            plotter_ui(self.plotter)

    def update(self, _: Any = None) -> None:
        if self.plotter:
            self.view_model.update_pyvista_volume(self.plotter)
+28 −0
Original line number Diff line number Diff line
"""Module for the Tab Content panel."""

from trame.widgets import vuetify3 as vuetify
from trame_server import Server

from ..view_models.main import MainViewModel
from ..views.plotly import PlotlyView
from ..views.pyvista import PyVistaView


class TabContentPanel:
    """View class to render content for a selected tab."""

    def __init__(self, server: Server, view_model: MainViewModel) -> None:
        self.view_model = view_model
        self.server = server
        self.ctrl = server.controller
        self.create_ui()

    def create_ui(self) -> None:
        with vuetify.VForm(ref="form") as self.f:
            with vuetify.VContainer(classes="pa-0", fluid=True):
                with vuetify.VCard():
                    with vuetify.VWindow(v_model="active_tab"):
                        with vuetify.VWindowItem(value=1):
                            PlotlyView(self.view_model)
                        with vuetify.VWindowItem(value=2):
                            PyVistaView(self.view_model)
+19 −0
Original line number Diff line number Diff line
"""Module for the Tab panel."""

from trame.widgets import vuetify3 as vuetify

from ..view_models.main import MainViewModel


class TabsPanel:
    """View class to render tabs."""

    def __init__(self, view_model: MainViewModel):
        self.view_model = view_model
        self.view_model.config_bind.connect("config")
        self.create_ui()

    def create_ui(self) -> None:
        with vuetify.VTabs(v_model=("active_tab", 0), classes="pl-5"):
            vuetify.VTab("Plotly", value=1)
            vuetify.VTab("PyVista", value=2)
Loading