Ejemplo n.º 1
0
class GanyScene(AppComponent):
    """Canvas for 3D surface mesh interactive plotting based on ipygany."""

    name = '3D Scene'

    def setup(self):
        vertices, triangle_indices = self.dataset._widgets.to_unstructured_mesh()

        elev_da = self.dataset._widgets.elevation
        elev_min = elev_da.min()
        elev_max = elev_da.max()
        elev_arr = self.dataset._widgets.current_elevation.values

        data = {
            'color': [Component(name='value', array=elev_arr, min=elev_min, max=elev_max)],
            'warp': [Component(name='value', array=elev_arr, min=elev_min, max=elev_max)],
        }

        self.polymesh = PolyMesh(vertices=vertices, triangle_indices=triangle_indices, data=data)
        self.isocolor = IsoColor(
            self.polymesh, input=('color', 'value'), min=elev_min, max=elev_max
        )
        self.warp = WarpByScalar(self.isocolor, input='warp', factor=1)
        self.scene = Scene([self.warp])

        return self.scene

    def redraw_isocolor_warp(self):
        """Trigger scene redraw if data slice has been updated."""
        new_warp_array = self.dataset._widgets.current_elevation.values
        new_color_array = self.dataset._widgets.current_color.values

        with self.scene.hold_sync():
            self.polymesh[('color', 'value')].array = new_color_array
            self.polymesh[('warp', 'value')].array = new_warp_array

    def reset_isocolor_limits(self, step=False):
        """Resets color limits to data range.

        Parameters
        ----------
        step : bool
            If true, resets the color range to the range of the data that is shown
            in the current scene. Otherwise (default), resets the color range to the
            whole data range.

        """
        if step:
            da = self.dataset._widgets.current_color
        else:
            da = self.dataset._widgets.color

        with self.scene.hold_sync():
            self.isocolor.min = da.min()
            self.isocolor.max = da.max()

    @property
    def linkable_traits(self):
        return [(self.scene, 'camera')]
Ejemplo n.º 2
0
def show_ipygany(plotter, return_viewer, height=None, width=None):
    """Show an ipygany scene."""
    # convert each mesh in the plotter to an ipygany scene
    actors = plotter.renderer._actors
    meshes = []
    for actor in actors.values():
        ipygany_obj = ipygany_block_from_actor(actor)
        if ipygany_obj is not None:
            meshes.append(ipygany_obj)

    bc_color = color_float_to_hex(*plotter.background_color)
    scene = Scene(meshes,
                  background_color=bc_color,
                  camera=ipygany_camera_from_plotter(plotter))

    # optionally size of the plotter
    if height is not None:
        scene.layout.height = f'{height}'
    if width is not None:
        scene.layout.width = f'{width}'

    cbar = None
    if len(plotter.scalar_bars):
        for mesh in meshes:
            if isinstance(mesh, ipygany.IsoColor):
                cbar = ipygany.ColorBar(mesh)
                colored_mesh = mesh
                break

    # Simply return the scene
    if return_viewer:
        return scene

    if cbar is not None:
        # Colormap choice widget
        colormap_dd = Dropdown(options=colormaps, description='Colormap:')
        jslink((colored_mesh, 'colormap'), (colormap_dd, 'index'))

        # sensible colorbar maximum width, or else it looks bad when
        # window is large.
        cbar.layout.max_width = '500px'
        cbar.layout.min_height = '50px'  # stop from getting squished
        # cbar.layout.height = '20%'  # stop from getting squished
        # cbar.layout.max_height = ''

        # Create a slider that will dynamically change the boundaries of the colormap
        # colormap_slider_range = FloatRangeSlider(value=[height_min, height_max],
        #                                          min=height_min, max=height_max,
        #                                          step=(height_max - height_min) / 100.)

        # jslink((colored_mesh, 'range'), (colormap_slider_range, 'value'))

        # create app
        title = HTML(value=f'<h3>{list(plotter.scalar_bars.keys())[0]}</h3>')
        legend = VBox((title, colormap_dd, cbar))
        scene = AppLayout(center=scene,
                          footer=legend,
                          pane_heights=[0, 0, '150px'])

    display.display_html(scene)
Ejemplo n.º 3
0
    def show_dem3d(self, dem):
        nr, nc = len(dem.y), len(dem.x)
        triangle_indices = np.empty((nr - 1, nc - 1, 2, 3), dtype='uint32')
        r = np.arange(nr * nc, dtype='uint32').reshape(nr, nc)
        triangle_indices[:, :, 0, 0] = r[:-1, :-1]
        triangle_indices[:, :, 1, 0] = r[:-1, 1:]
        triangle_indices[:, :, 0, 1] = r[:-1, 1:]
        triangle_indices[:, :, 1, 1] = r[1:, 1:]
        triangle_indices[:, :, :, 2] = r[1:, :-1, None]
        triangle_indices.shape = (-1, 3)

        height_component = Component(name='value', array=self.alt)

        mesh = PolyMesh(vertices=self.vertices,
                        triangle_indices=triangle_indices,
                        data={'height': [height_component]})

        colored_mesh = IsoColor(mesh,
                                input='height',
                                min=np.min(self.alt),
                                max=np.max(self.alt))
        warped_mesh = Warp(colored_mesh,
                           input=(0, 0, ('height', 'value')),
                           factor=0)
        warp_slider = FloatSlider(min=0,
                                  max=10,
                                  value=0,
                                  description='Vertical exaggeration',
                                  style={'description_width': 'initial'})

        jslink((warped_mesh, 'factor'), (warp_slider, 'value'))

        self.dem3d.clear_output()
        with self.dem3d:
            display(VBox((Scene([warped_mesh]), warp_slider)))
Ejemplo n.º 4
0
def render_ipyany_scene(visualize: Visualize, off_screen_file=False):
    from ipygany import Scene

    meshes = []
    for obj in visualize.objects:
        meshes.append(obj.convert_to_ipygany_mesh())
    return Scene(meshes)
Ejemplo n.º 5
0
    def setup(self):
        vertices, triangle_indices = self.dataset._widgets.to_unstructured_mesh()

        elev_da = self.dataset._widgets.elevation
        elev_min = elev_da.min()
        elev_max = elev_da.max()
        elev_arr = self.dataset._widgets.current_elevation.values

        data = {
            'color': [Component(name='value', array=elev_arr, min=elev_min, max=elev_max)],
            'warp': [Component(name='value', array=elev_arr, min=elev_min, max=elev_max)],
        }

        self.polymesh = PolyMesh(vertices=vertices, triangle_indices=triangle_indices, data=data)
        self.isocolor = IsoColor(
            self.polymesh, input=('color', 'value'), min=elev_min, max=elev_max
        )
        self.warp = WarpByScalar(self.isocolor, input='warp', factor=1)
        self.scene = Scene([self.warp])

        return self.scene
Ejemplo n.º 6
0
colored_mesh = IsoColor(mesh,
                        input=('S', 'YY'),
                        min=sigmayy_min,
                        max=sigmayy_max)

# Create a slider that will dynamically change the boundaries of the colormap
colormap_slider_range = FloatRangeSlider(value=[sigmayy_min, sigmayy_max],
                                         min=sigmayy_min,
                                         max=sigmayy_max,
                                         step=(sigmayy_max - sigmayy_min) /
                                         100.)

jslink((colored_mesh, 'range'), (colormap_slider_range, 'value'))

# Create a colorbar widget
colorbar = ColorBar(colored_mesh)

# Colormap choice widget
colormap = Dropdown(options=colormaps, description='colormap:')

jslink((colored_mesh, 'colormap'), (colormap, 'index'))

AppLayout(
    header=Scene([colored_mesh]),
    left_sidebar=VBox((colormap, colormap_slider_range)),
    right_sidebar=(colorbar),
    pane_widths=[1, 0, 1],
    pane_heights=['80%', '20%', 0],
    footer=None,
)
Ejemplo n.º 7
0
#!/usr/bin/env python
# coding: utf-8

# In[1]:

from ipygany import Scene, TetraMesh
mesh = TetraMesh.from_vtk('benchmarks/000-tuning-fork/fork.vtk')
scene = Scene([mesh])
scene
Ejemplo n.º 8
0
def getNotebookBackend(actors2show, zoom, viewup):

    vp = settings.plotter_instance

    if zoom == 'tight':
        zoom=1 # disable it

    if isinstance(vp.shape, str) or sum(vp.shape) > 2:
        colors.printc("Multirendering is not supported in jupyter.", c=1)
        return

    ####################################################################################
    # https://github.com/InsightSoftwareConsortium/itkwidgets
    #  /blob/master/itkwidgets/widget_viewer.py
    if 'itk' in settings.notebookBackend:
        from itkwidgets import view

        settings.notebook_plotter = view(actors=actors2show,
                                         cmap='jet', ui_collapsed=True,
                                         gradient_opacity=False)


    ####################################################################################
    elif settings.notebookBackend == 'k3d':
        try:
            import k3d # https://github.com/K3D-tools/K3D-jupyter
        except:
            print("Cannot find k3d, install with:  pip install k3d")
            return

        actors2show2 = []
        for ia in actors2show:
            if not ia:
                continue
            if isinstance(ia, vtk.vtkAssembly): #unpack assemblies
                acass = ia.unpack()
                actors2show2 += acass
            else:
                actors2show2.append(ia)

        # vbb, sizes, _, _ = addons.computeVisibleBounds()
        # kgrid = vbb[0], vbb[2], vbb[4], vbb[1], vbb[3], vbb[5]

        settings.notebook_plotter = k3d.plot(axes=[vp.xtitle, vp.ytitle, vp.ztitle],
                                             menu_visibility=settings.k3dMenuVisibility,
                                             height=settings.k3dPlotHeight,
                                             antialias=settings.k3dAntialias,
                                             )
        # settings.notebook_plotter.grid = kgrid
        settings.notebook_plotter.lighting = settings.k3dLighting

        # set k3d camera
        settings.notebook_plotter.camera_auto_fit = settings.k3dCameraAutoFit
        settings.notebook_plotter.grid_auto_fit = settings.k3dGridAutoFit

        settings.notebook_plotter.axes_helper = settings.k3dAxesHelper

        if settings.plotter_instance and settings.plotter_instance.camera:
            k3dc =  utils.vtkCameraToK3D(settings.plotter_instance.camera)
            if zoom:
                k3dc[0] /= zoom
                k3dc[1] /= zoom
                k3dc[2] /= zoom
            settings.notebook_plotter.camera = k3dc
        # else:
        #     vsx, vsy, vsz = vbb[0]-vbb[1], vbb[2]-vbb[3], vbb[4]-vbb[5]
        #     vss = numpy.linalg.norm([vsx, vsy, vsz])
        #     if zoom:
        #         vss /= zoom
        #     vfp = (vbb[0]+vbb[1])/2, (vbb[2]+vbb[3])/2, (vbb[4]+vbb[5])/2 # camera target
        #     if viewup == 'z':
        #         vup = (0,0,1) # camera up vector
        #         vpos= vfp[0] + vss/1.9, vfp[1] + vss/1.9, vfp[2]+vss*0.01  # camera position
        #     elif viewup == 'x':
        #         vup = (1,0,0)
        #         vpos= vfp[0]+vss*0.01, vfp[1] + vss/1.5, vfp[2]  # camera position
        #     else:
        #         vup = (0,1,0)
        #         vpos= vfp[0]+vss*0.01, vfp[1]+vss*0.01, vfp[2] + vss/1.5  # camera position
        #     settings.notebook_plotter.camera = [vpos[0], vpos[1], vpos[2],
        #                                           vfp[0],  vfp[1],  vfp[2],
        #                                           vup[0],  vup[1],  vup[2] ]
        if not vp.axes:
            settings.notebook_plotter.grid_visible = False

        for ia in actors2show2:

            if isinstance(ia, (vtk.vtkCornerAnnotation, vtk.vtkAssembly)):
                continue

            kobj = None
            kcmap= None
            name = None
            if hasattr(ia, 'filename'):
                if ia.filename:
                    name = os.path.basename(ia.filename)
                if ia.name:
                    name = os.path.basename(ia.name)

            #####################################################################scalars
            # work out scalars first, Points Lines are also Mesh objs
            if isinstance(ia, (Mesh, shapes.Line, Points)):
#                print('scalars', ia.name, ia.N())
                iap = ia.GetProperty()

                if isinstance(ia, (shapes.Line, Points)):
                    iapoly = ia.polydata()
                else:
                    iapoly = ia.clone().clean().triangulate().computeNormals().polydata()

                vtkscals = None
                color_attribute = None
                if ia.mapper().GetScalarVisibility():
                    vtkdata = iapoly.GetPointData()
                    vtkscals = vtkdata.GetScalars()

                    if vtkscals is None:
                        vtkdata = iapoly.GetCellData()
                        vtkscals = vtkdata.GetScalars()
                        if vtkscals is not None:
                            c2p = vtk.vtkCellDataToPointData()
                            c2p.SetInputData(iapoly)
                            c2p.Update()
                            iapoly = c2p.GetOutput()
                            vtkdata = iapoly.GetPointData()
                            vtkscals = vtkdata.GetScalars()

                    if vtkscals is not None:
                        if not vtkscals.GetName():
                            vtkscals.SetName('scalars')
                        scals_min, scals_max = ia.mapper().GetScalarRange()
                        color_attribute = (vtkscals.GetName(), scals_min, scals_max)
                        lut = ia.mapper().GetLookupTable()
                        lut.Build()
                        kcmap=[]
                        nlut = lut.GetNumberOfTableValues()
                        for i in range(nlut):
                            r,g,b,a = lut.GetTableValue(i)
                            kcmap += [i/(nlut-1), r,g,b]


            #####################################################################Volume
            if isinstance(ia, Volume):
#                print('Volume', ia.name, ia.dimensions())
                kx, ky, kz = ia.dimensions()
                arr = ia.pointdata[0]
                kimage = arr.reshape(-1, ky, kx)

                colorTransferFunction = ia.GetProperty().GetRGBTransferFunction()
                kcmap=[]
                for i in range(128):
                    r,g,b = colorTransferFunction.GetColor(i/127)
                    kcmap += [i/127, r,g,b]

                kbounds = numpy.array(ia.imagedata().GetBounds()) \
                    + numpy.repeat(numpy.array(ia.imagedata().GetSpacing()) / 2.0, 2)\
                    * numpy.array([-1,1] * 3)

                kobj = k3d.volume(kimage.astype(numpy.float32),
                                  color_map=kcmap,
                                  #color_range=ia.imagedata().GetScalarRange(),
                                  alpha_coef=10,
                                  bounds=kbounds,
                                  name=name,
                                  )
                settings.notebook_plotter += kobj

            #####################################################################text
            elif hasattr(ia, 'info') and 'formula' in ia.info.keys():
                pos = (ia.GetPosition()[0],ia.GetPosition()[1])
                kobj = k3d.text2d(ia.info['formula'], position=pos)
                settings.notebook_plotter += kobj


            #####################################################################Mesh
            elif isinstance(ia, Mesh) and ia.N() and len(ia.faces()):
                # print('Mesh', ia.name, ia.N(), len(ia.faces()))
                kobj = k3d.vtk_poly_data(iapoly,
                                         name=name,
                                         # color=_rgb2int(iap.GetColor()),
                                         color_attribute=color_attribute,
                                         color_map=kcmap,
                                         opacity=iap.GetOpacity(),
                                         wireframe=(iap.GetRepresentation()==1))

                if iap.GetInterpolation() == 0:
                    kobj.flat_shading = True
                settings.notebook_plotter += kobj

            #####################################################################Points
            elif isinstance(ia, Points):
                # print('Points', ia.name, ia.N())
                kcols=[]
                if color_attribute is not None:
                    scals = utils.vtk2numpy(vtkscals)
                    kcols = k3d.helpers.map_colors(scals, kcmap,
                                                   [scals_min,scals_max]).astype(numpy.uint32)
                # sqsize = numpy.sqrt(numpy.dot(sizes, sizes))

                kobj = k3d.points(ia.points().astype(numpy.float32),
                                  color=_rgb2int(iap.GetColor()),
                                  colors=kcols,
                                  opacity=iap.GetOpacity(),
                                  shader=settings.k3dPointShader,
                                  point_size=iap.GetPointSize(),
                                  name=name,
                                  )
                settings.notebook_plotter += kobj


            #####################################################################Lines
            elif ia.polydata(False).GetNumberOfLines():
                # print('Line', ia.name, ia.N(), len(ia.faces()),
                #       ia.polydata(False).GetNumberOfLines(), len(ia.lines()),
                #       color_attribute, [vtkscals])

                # kcols=[]
                # if color_attribute is not None:
                #     scals = utils.vtk2numpy(vtkscals)
                #     kcols = k3d.helpers.map_colors(scals, kcmap,
                #                                    [scals_min,scals_max]).astype(numpy.uint32)

                # sqsize = numpy.sqrt(numpy.dot(sizes, sizes))

                for i, ln_idx in enumerate(ia.lines()):
                    if i>200:
                        print('WARNING: K3D nr of line segments is limited to 200.')
                        break
                    pts = ia.points()[ln_idx]
                    kobj = k3d.line(pts.astype(numpy.float32),
                                    color=_rgb2int(iap.GetColor()),
                                    opacity=iap.GetOpacity(),
                                    shader=settings.k3dLineShader,
                                    # width=iap.GetLineWidth()*sqsize/1000,
                                    name=name,
                                    )

                    settings.notebook_plotter += kobj


    ####################################################################################
    elif settings.notebookBackend == 'panel' and hasattr(vp, 'window') and vp.window:

        import panel # https://panel.pyviz.org/reference/panes/VTK.html
        vp.renderer.ResetCamera()
        settings.notebook_plotter = panel.pane.VTK(vp.window,
                                                   width=int(vp.size[0]/1.5),
                                                   height=int(vp.size[1]/2))


    ####################################################################################
    elif 'ipyvtk' in settings.notebookBackend and hasattr(vp, 'window') and vp.window:

        from ipyvtklink.viewer import ViewInteractiveWidget
        vp.renderer.ResetCamera()
        settings.notebook_plotter = ViewInteractiveWidget(vp.window)

    ####################################################################################
    elif 'ipygany' in settings.notebookBackend:

        from ipygany import PolyMesh, Scene, IsoColor, RGB, Component
        from ipygany import Alpha, ColorBar, colormaps, PointCloud
        from ipywidgets import FloatRangeSlider, Dropdown, VBox, AppLayout, jslink

        bgcol = colors.rgb2hex(colors.getColor(vp.backgrcol))

        actors2show2 = []
        for ia in actors2show:
            if not ia:
                continue
            if isinstance(ia, vedo.Assembly): #unpack assemblies
                assacts = ia.unpack()
                for ja in assacts:
                    if isinstance(ja, vedo.Assembly):
                        actors2show2 += ja.unpack()
                    else:
                        actors2show2.append(ja)
            else:
                actors2show2.append(ia)

        pmeshes = []
        colorbar = None
        for obj in actors2show2:
#            print("ipygany processing:", [obj], obj.name)

            if isinstance(obj, vedo.shapes.Line):
                lg = obj.diagonalSize()/1000 * obj.GetProperty().GetLineWidth()
                vmesh = vedo.shapes.Tube(obj.points(), r=lg, res=4).triangulate()
                vmesh.c(obj.c())
                faces = vmesh.faces()
                # todo: Lines
            elif isinstance(obj, Mesh):
                vmesh = obj.triangulate()
                faces = vmesh.faces()
            elif isinstance(obj, Points):
                vmesh = obj
                faces = []
            elif isinstance(obj, Volume):
                vmesh = obj.isosurface()
                faces = vmesh.faces()
            elif isinstance(obj, vedo.TetMesh):
                vmesh = obj.tomesh(fill=False)
                faces = vmesh.faces()
            else:
                print("ipygany backend: cannot process object type", [obj])
                continue

            vertices = vmesh.points()
            scals = vmesh.inputdata().GetPointData().GetScalars()
            if scals and not colorbar: # there is an active array, only pick the first
                aname = scals.GetName()
                arr = vmesh.pointdata[aname]
                parr = Component(name=aname, array=arr)
                if len(faces):
                    pmesh = PolyMesh(vertices=vertices, triangle_indices=faces, data={aname: [parr]})
                else:
                    pmesh = PointCloud(vertices=vertices, data={aname: [parr]})
                rng = scals.GetRange()
                colored_pmesh = IsoColor(pmesh, input=aname, min=rng[0], max=rng[1])
                if obj.scalarbar:
                    colorbar = ColorBar(colored_pmesh)
                    colormap_slider_range = FloatRangeSlider(value=rng,
                                                             min=rng[0], max=rng[1],
                                                             step=(rng[1] - rng[0]) / 100.)
                    jslink((colored_pmesh, 'range'), (colormap_slider_range, 'value'))
                    colormap = Dropdown(
                        options=colormaps,
                        description='Colormap:'
                    )
                    jslink((colored_pmesh, 'colormap'), (colormap, 'index'))

            else:
                if len(faces):
                    pmesh = PolyMesh(vertices=vertices, triangle_indices=faces)
                else:
                    pmesh = PointCloud(vertices=vertices)
                if vmesh.alpha() < 1:
                    colored_pmesh = Alpha(RGB(pmesh, input=tuple(vmesh.color())), input=vmesh.alpha())
                else:
                    colored_pmesh = RGB(pmesh, input=tuple(vmesh.color()))

            pmeshes.append(colored_pmesh)

        if colorbar:
            scene = AppLayout(
                    left_sidebar=Scene(pmeshes, background_color=bgcol),
                    right_sidebar=VBox((colormap_slider_range, #not working
                                        colorbar,
                                        colormap)),
                    pane_widths=[2, 0, 1],
            )
        else:
            scene = Scene(pmeshes, background_color=bgcol)

        settings.notebook_plotter = scene



    ####################################################################################
    elif '2d' in settings.notebookBackend.lower() and hasattr(vp, 'window') and vp.window:
        import PIL.Image
        try:
            import IPython
        except ImportError:
            raise Exception('IPython not available.')

        from vedo.io import screenshot
        settings.screeshotLargeImage = True
        nn = screenshot(returnNumpy=True, scale=settings.screeshotScale+2)
        pil_img = PIL.Image.fromarray(nn)
        settings.notebook_plotter = IPython.display.display(pil_img)

    return settings.notebook_plotter
Ejemplo n.º 9
0
def visualize_it(res_file, temp_dir=".temp", default_index=0):
    import pathlib

    import meshio
    from ipygany import ColorBar, IsoColor, PolyMesh, Scene, Warp, colormaps
    from IPython.display import clear_output, display
    from ipywidgets import AppLayout, Dropdown, FloatSlider, VBox, jslink

    from ada.core.vector_utils import vector_length

    res_file = pathlib.Path(res_file).resolve().absolute()
    suffix = res_file.suffix.lower()

    suffix_map = {".rmed": "med", ".vtu": None}

    imesh = meshio.read(res_file, file_format=suffix_map[suffix])
    imesh.point_data = {
        key.replace(" ", "_"): value
        for key, value in imesh.point_data.items()
    }

    def filter_keys(var):
        if suffix == ".vtu" and var != "U":
            return False
        if suffix == ".rmed" and var == "point_tags":
            return False
        return True

    warp_data = [key for key in filter(filter_keys, imesh.point_data.keys())]
    magn_data = []
    for d in warp_data:
        res = [vector_length(v[:3]) for v in imesh.point_data[d]]
        res_norm = [r / max(res) for r in res]
        magn_data_name = f"{d}_magn"
        imesh.point_data[magn_data_name] = np.array(res_norm, dtype=np.float64)
        magn_data.append(magn_data_name)

    imesh.field_data = {
        key: np.array(value)
        for key, value in imesh.field_data.items()
    }

    tf = (pathlib.Path(temp_dir).resolve().absolute() /
          res_file.name).with_suffix(".vtu")

    if tf.exists():
        os.remove(tf)
    os.makedirs(tf.parent, exist_ok=True)
    imesh.write(tf)

    mesh = PolyMesh.from_vtk(str(tf))
    mesh.default_color = "gray"

    warp_vec = warp_data[default_index]
    try:
        colored_mesh = IsoColor(mesh,
                                input=magn_data[default_index],
                                min=0.0,
                                max=1.0)
    except KeyError as e:
        trace_str = traceback.format_exc()
        logging.error(f'KeyError "{e}"\nTrace: "{trace_str}"')
        colored_mesh = mesh
    except ImportError as e:
        trace_str = traceback.format_exc()
        logging.error("This might be")
        logging.error(f'ImportError "{e}"\nTrace: "{trace_str}"')
        return

    warped_mesh = Warp(colored_mesh, input=warp_vec, warp_factor=0.0)

    warp_slider = FloatSlider(value=0.0, min=-1.0, max=1.0)

    jslink((warped_mesh, "factor"), (warp_slider, "value"))

    # Create a colorbar widget
    colorbar = ColorBar(colored_mesh)

    # Colormap choice widget
    colormap = Dropdown(options=colormaps, description="colormap:")

    jslink((colored_mesh, "colormap"), (colormap, "index"))

    # EigenValue choice widget
    eig_map = Dropdown(options=warp_data, description="Data Value:")

    scene = Scene([warped_mesh])
    app = AppLayout(left_sidebar=scene,
                    right_sidebar=VBox(
                        (eig_map, warp_slider, colormap, colorbar)),
                    pane_widths=[2, 0, 1])

    def change_input(change):
        vec_name = change["new"]
        logging.info(vec_name)
        colored_mesh.input = vec_name + "_magn"
        warped_mesh.input = vec_name
        # Highly inefficient but likely needed due to bug https://github.com/QuantStack/ipygany/issues/69
        clear_output()
        display(app)

    eig_map.observe(change_input, names=["value"])

    return app