provide virtual WebGL support
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/[email protected]/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:
- It's expected to have some performance impact, so if you only have one or two WebGL graphs at a time you're likely better off not using it, though we haven't quantified this.
- It takes over ALL WebGL on the page, not just plotly.js. This could be good, as you can then put an unlimited number of WebGL-based components of any type on the page, however we haven't tested it with other components so it's possible there will be issues. Aside from plotly.js (
dcc.Graph), WebGL is used by somedash-biocomponents,dash-vtk,dash-deck, and possibly others (I don't thinkdash-cytoscapeordash-leafletuse WebGL but I'm not 100% sure of 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.
@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:
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/[email protected]/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)
Perhaps @graingert-coef or @vogt31337 could be enticed to test out dash-deck with virtual webgl, and provide a more complete test case?