Efficient polygon drawing

Hi all,

For a project I am trying to draw thousands of polygons in a chart.
These polygons can have an x number of points.

Currently I use Scatter and use fill to self. This gives me a chart with x amount of polygons.
However needing to draw thousands is very slow. 3k+ results in multiple seconds waiting time.

The scatters need to be individually clickable as there is meta data behind them which i need.

Is there a way to achieve this behavior more efficiently?

@Ruben1998 welcome to the forums.

Strange use case, maybe reconsider your approach. Shapes might be an alternative but it depends on your requirements:

1 Like

It seems that you have not used Scattergl before. Maybe you can try to replace go.Scatter with go.Scattergl. It renders faster when you have many points to display. It may not provide all interactive features of go.Scatter, but the click event indeed works.

I append an example modified from the official document. It renders 90,000 points. If you do not use Scattergl here, the figure will be extremely slow.

from dash import Dash, dcc, html, Input, Output, callback

import itertools

import plotly.graph_objects as go

import json
import pandas as pd

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = Dash(__name__, external_stylesheets=external_stylesheets)

styles = {
    'pre': {
        'border': 'thin lightgrey solid',
        'overflowX': 'scroll'
    }
}

axis = tuple(range(300))
points = tuple(zip(*itertools.product(axis, axis)))

df = pd.DataFrame({
    "x": points[0],
    "y": points[1],
    "customdata": points[1]
})

fig = go.Figure(go.Scattergl(x=df["x"], y=df["y"], mode="markers", customdata=df["customdata"]))

fig.update_layout(clickmode='event+select')

fig.update_traces(marker_size=20)

app.layout = html.Div([
    dcc.Graph(
        id='basic-interactions',
        style={'width': '100%', 'height': '45vh'},
        figure=fig
    ),

    html.Div(className='row', children=[
        html.Div([
            dcc.Markdown("""
                **Hover Data**

                Mouse over values in the graph.
            """),
            html.Pre(id='hover-data', style=styles['pre'])
        ], className='three columns'),

        html.Div([
            dcc.Markdown("""
                **Click Data**

                Click on points in the graph.
            """),
            html.Pre(id='click-data', style=styles['pre']),
        ], className='three columns'),

        html.Div([
            dcc.Markdown("""
                **Selection Data**

                Choose the lasso or rectangle tool in the graph's menu
                bar and then select points in the graph.

                Note that if `layout.clickmode = 'event+select'`, selection data also
                accumulates (or un-accumulates) selected data if you hold down the shift
                button while clicking.
            """),
            html.Pre(id='selected-data', style=styles['pre']),
        ], className='three columns'),

        html.Div([
            dcc.Markdown("""
                **Zoom and Relayout Data**

                Click and drag on the graph to zoom or click on the zoom
                buttons in the graph's menu bar.
                Clicking on legend items will also fire
                this event.
            """),
            html.Pre(id='relayout-data', style=styles['pre']),
        ], className='three columns')
    ])
])


@callback(
    Output('hover-data', 'children'),
    Input('basic-interactions', 'hoverData'))
def display_hover_data(hoverData):
    return json.dumps(hoverData, indent=2)


@callback(
    Output('click-data', 'children'),
    Input('basic-interactions', 'clickData'))
def display_click_data(clickData):
    return json.dumps(clickData, indent=2)


@callback(
    Output('selected-data', 'children'),
    Input('basic-interactions', 'selectedData'))
def display_selected_data(selectedData):
    return json.dumps(selectedData, indent=2)


@callback(
    Output('relayout-data', 'children'),
    Input('basic-interactions', 'relayoutData'))
def display_relayout_data(relayoutData):
    return json.dumps(relayoutData, indent=2)


if __name__ == '__main__':
	app.run(host="0.0.0.0", port="8080", debug=True)
2 Likes

ScatterGL does not improve the performance. I suspect the issue lays in 3k+ SVG being needed to be rendered by the browser. I inspected the raw html and each SVG has its own HTML Tag. That looks innefficient.

I will try the shape approach and for the clickable part putting markers on the centroid of the polygon will allow me to reach my use case.

I do not know the details of your figure. In my own experience, if using Scattergl, the lines and markers will not be rendered as SVG elements, and the figure should be rendered as a canvas. That’s why the speed is faster with Scattergl.

However, if you are using shapes, I believe that would not help. In Scattergl, the markers will be rendered inside the canvas, but the shapes are still thousands of SVG elements, which lowers the performance significantly.

I am just sharing my own experience. If you would like to create a simple demo to show your use case, it will help others understand how to tackle the issue.

1 Like