plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.34k stars 2.06k forks source link

provide virtual WebGL support #2740

Open alexcjohnson opened 8 months ago

alexcjohnson commented 8 months ago

Plotly.js v2.28.0 added support for virtual-webgl, which allows you to put arbitrarily many WebGL-based graphs on a single page, as opposed to the limit of typically 4-8 before you hit the maximum contexts a browser will give you (note most desktop browsers give you up to 16 contexts, but one plotly.js graph uses up to 3 contexts). We would like to expose this option in Dash too. It can already be done by adding https://unpkg.com/virtual-webgl@1.0.6/src/virtual-webgl.js to external_scripts (or downloading that and putting it in your assets folder), so we could just document that, but it would be nicer to have a built-in way to do this.

Two important things to note about this:

Initially I was thinking we would make this opt-in as a prop of dcc.Graph, but because of the global (and irreversible) nature of this I'm now thinking a global setting like a Dash() constructor arg would be better.

emilykl commented 6 months ago

@alexcjohnson

I have tested all WebGL dash-bio components, as well as dash-vtk with the shared WebGL context, and all seem to be working fine as far as I can tell.

dash-deck I'm less familiar with; the basic "Hello World" example seems to be loading OK but I haven't yet been able to put together a more comprehensive example to test with.

Sample Dash app used for testing: ```python import urllib.request as urlreq import dash from dash import dcc, html, callback, Input, Output import dash_bio from dash_bio.utils import ngl_parser, xyz_reader import dash_deck import dash_vtk import numpy as np import pandas as pd import plotly.graph_objects as go from pyvista import examples app = dash.Dash( __name__, external_scripts=[ "https://unpkg.com/virtual-webgl@1.0.6/src/virtual-webgl.js", # Enable shared virtual WebGL context ], ) server = app.server def layout(): return html.Div( [ dcc.Dropdown( id="content-dropdown", options=[ {"label": x, "value": x} for x in ["dcc-graph", "dash-bio", "dash-vtk", "dash-deck"] ], value="dcc-graph", ), dcc.Loading(html.Div(id="content")), ] ) @callback( Output("content", "children"), Input("content-dropdown", "value"), ) def select_content(value): if value == "dcc-graph": return dcc_graphs_webgl() elif value == "dash-bio": return dash_bio_components() elif value == "dash-vtk": return dash_vtk_components() elif value == "dash-deck": return dash_deck_components() else: return html.Div(f"Invalid selection `{value}`") def dcc_graphs_webgl(): return html.Div( [ dcc.Graph(figure=go.Figure(data=[go.Scattergl(x=[1, 2, 3], y=[1, 3, 2])])), ] ) def dash_bio_components(): return html.Div( [ # # Speck html.Div("Speck"), html.Div( dash_bio.Speck( id="default-speck", data=xyz_reader.read_xyz( datapath_or_datastring=urlreq.urlopen( "https://git.io/speck_methane.xyz" ) .read() .decode("utf-8"), is_datafile=False, ), ), style={"border": "2px solid black"}, ), # NglMoleculeViewer html.Div("NglMoleculeViewer"), html.Div( dash_bio.NglMoleculeViewer( id="default-ngl-molecule", data=[ ngl_parser.get_data( data_path="https://raw.githubusercontent.com/plotly/datasets/master/Dash_Bio/Molecular/", pdb_id="1BNA", color="red", reset_view=True, local=False, ) ], ), style={"border": "2px solid black"}, ), # AlignmentChart html.Div("AlignmentChart"), html.Div( dash_bio.AlignmentChart( id="my-default-alignment-viewer", data=urlreq.urlopen("https://git.io/alignment_viewer_p53.fasta") .read() .decode("utf-8"), height=900, tilewidth=30, ), style={"border": "2px solid black"}, ), # ManhattanPlot html.Div("ManhattanPlot"), html.Div( dcc.Graph( id="default-dashbio-manhattanplot", figure=dash_bio.ManhattanPlot( dataframe=pd.read_csv("https://git.io/manhattan_data.csv") ), ), style={"border": "2px solid black"}, ), ] ) def dash_vtk_components(): return html.Div( [ # vtk view with point cloud vtk_view_point_cloud(), ] ) def vtk_view_point_cloud(): # Get point cloud data from PyVista dataset = examples.download_lidar() subset = 0.2 selection = np.random.randint( low=0, high=dataset.n_points - 1, size=int(dataset.n_points * subset) ) points = dataset.points[selection] xyz = points.ravel() elevation = points[:, -1].ravel() min_elevation = np.amin(elevation) max_elevation = np.amax(elevation) return html.Div( dash_vtk.View( [ dash_vtk.PointCloudRepresentation( xyz=xyz, scalars=elevation, colorDataRange=[min_elevation, max_elevation], property={"pointSize": 2}, ) ] ), style={"height": "400px", "width": "600px"}, ) # How to keep this from taking over entire browser canvas? def dash_deck_components(): data = { "description": "A minimal deck.gl example", "initialViewState": {"longitude": -122.45, "latitude": 37.8, "zoom": 12}, "layers": [ { "@@type": "TextLayer", "data": [{"position": [-122.45, 37.8], "text": "Hello World"}], }, ], } return html.Div( dash_deck.DeckGL(data=data, id="deck-gl"), style={"width": "600px", "height": "400px"}, ) app.layout = layout if __name__ == "__main__": app.run_server(debug=True, port=8050) ```
alexcjohnson commented 6 months ago

Perhaps @graingert-coef or @vogt31337 could be enticed to test out dash-deck with virtual webgl, and provide a more complete test case?