Pan, zoom performance issue when using hatch pattern in multiple glyphs

I am using hatch_pattern to indicate specific feature on a subset of my data.
My data is around 400 observations where I am using the hatch_pattern on around 60 % of the data.

In my MRE example below I have 400 circles. The circle glyph’s are plotted as expected with the hatch pattern in around 66 % of the circles. However, when one pan the plot or zoom in/out there is some delay in responsiveness. If I do not use hatch pattern at all the pan/zooming is excellent (plot no 3).

I have tried to split my data into 2 CDS, one with data w/o hatch_pattern, and the other with hattch_pattern (plot no 2). Did not observe any improvement.

Have tried output_backend=webGL. Did not observe any improvement.

Have tried adjusting lod_threshold. Did not observe any improvement.

Is there something I am doing wrong in my data setup when using hatch_pattern? Is there an argument I am missing in order to improve performance? Can I tune when hatch_pattern is updated/redrawn after an event?

Bokeh 3.6.2. Tested on Linux, Windows, MacOS. Firefox, Edge.

import pandas as pd
from bokeh.io import output_file, curdoc, save
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.layouts import row

data = {
    'x': [],
    'y': []
}
for y in range(1, 21):
    for x in range(1, 21):
        data['x'].append(x)
        data['y'].append(y)
df = pd.DataFrame(data)

df['mod_3'] = (df['x']*df['y']) % 3
df['id'] = 'A'
df.loc[df['mod_3'] == 1, 'id'] = 'B'
df.loc[df['mod_3'] == 2, 'id'] = 'C'

df['radii'] = 0.3
df.loc[df['id'] == 'B', 'radii'] = 0.4
df.loc[df['id'] == 'C', 'radii'] = 0.5

df['color'] = 'orange'
df.loc[df['id'] == 'B', 'color'] = 'red'
df.loc[df['id'] == 'C', 'color'] = 'navy'

df['hatch'] = ' '
df.loc[df['id'] != 'A', 'hatch'] = '/'
df['hatch_color'] = 'grey'
df.loc[df['id'] == 'C', 'hatch_color'] = 'black'

src = ColumnDataSource(data = df)
p = figure(title = 'One CDS with empty hatch and "/" hatch')
p.circle(
    x = 'x',
    y = 'y',
    radius = 'radii',
    fill_color = 'color',
    fill_alpha = 0.7,
    hatch_pattern = 'hatch',
    hatch_color = 'hatch_color',
    hatch_weight = 2,
    line_color = 'white',
    source = src
)

src_no_hatch = ColumnDataSource(data = df[df['hatch'] == ' '])
src_hatch = ColumnDataSource(data = df[df['hatch'] != ''])
p2 = figure(title = '2 CDS, one with no hatch, the other with hatch')
p2.circle(
    x = 'x',
    y = 'y',
    radius = 'radii',
    fill_color = 'color',
    fill_alpha = 0.7,
    line_color = 'white',
    source = src_no_hatch
)
p2.circle(
    x = 'x',
    y = 'y',
    radius = 'radii',
    fill_color = 'color',
    fill_alpha = 0.7,
    hatch_pattern = 'hatch',
    hatch_color = 'hatch_color',
    hatch_weight = 2,
    line_color = 'white',
    source = src_hatch
)

p3 = figure(title = 'One CDS, no hatch used')
p3.circle(
    x = 'x',
    y = 'y',
    radius = 'radii',
    fill_color = 'color',
    fill_alpha = 0.7,
    line_color = 'white',
    source = src
)
save(row(p, p2, p3))

I don’t think any evaluation of performance with respect to hatch filling has ever been performed, so it’s entirely possible this is just how things are. Certainly when I added the feature I only had in mind use cases with relatively few (tens) of “large” filled regions, under the rationale that identifying hatch patterns for hundred or thousands of things would be difficult.

All that said IIRC (it’s be awhile) the hatch codepaths are only very slightly different than solid fills, i.e. just one or two extra lines of HTML canvas API calls. So this might just be browser limitation. It could also potentially be very different on different browsers. I’d encourage you to see how things look on a variety of platforms.

Good to know the background of using hatch filling @Bryan. I have tested this in Firefox on Linux and MacOS. Safari on MacOS and in Microsoft Edge on Windows. I observe the same negative performance on all platforms and browsers.

I’ll try to take a very quick look at a profile run with the code above. I expect that pattern fill are just expensive at the browser level, but it there’s any immediately obvious low-hanging fruit on the Bokeh side, I will make and issue with the details.

Edit: I guess I am surprised that raising LOD settings did not improve things, though, can you be more specific about what you tried, exactly?

1 Like

With respect LOD I tried to reduce lod_threshold from the default 2000 to 200. But 200 plotted glyphs still was too many in order to observe any significant performance gain.

So, after looking at a profile, I do wonder if Bokeh is doing some image preparation work on every paint that could be cached or something. @Jonas_Grave_Kristens can you make an issue with your details and code and I will add my profile observations to it.

Thanks for taking the time looking into this @Bryan.
I have created issue 14232 on Github.