Skip to content

Keep ScatterGL in sync when an axis scale's type changes at runtime#1699

Open
astrofrog wants to merge 2 commits intobqplot:0.12.xfrom
astrofrog:fix-scattergl-scale-update
Open

Keep ScatterGL in sync when an axis scale's type changes at runtime#1699
astrofrog wants to merge 2 commits intobqplot:0.12.xfrom
astrofrog:fix-scattergl-scale-update

Conversation

@astrofrog
Copy link
Copy Markdown
Contributor

ScatterGL renders points via a WebGL shader (shaders/scales.glsl, shaders/scatter-vertex.glsl) that uses compile-time preprocessor directives to pick between a linear and a log transform:

#if SCALE_TYPE_x == 2
    vec4 p = scale_transform_log(...);
#else
    vec4 p = scale_transform_linear(...);
#endif

(see https://github.com/bloomberg/bqplot/blob/861aa219a216281c0dc3d910701815949e5b5615/js/shaders/scatter-vertex.glsl#L104-L113)

The SCALE_TYPE_x / SCALE_TYPE_y defines are set from scales.x.model.type at the point where the three.js ShaderMaterial is first constructed, and never refreshed afterwards. That was fine when the only way to "switch to log" was to replace the scale object entirely (which triggered a full re-init).

However, I've developed a new scale and axis implementation in bqplot-linlog can mutate its own type at runtime — which flips model.type between linear and log without replacing the scale. However, when doing this ScatterGL gets left behind: the axis ticks and SVG marks update, but the GL points keep drawing with whichever transform happened to be compiled in at init time.

LinesGL in bqplot-image-gl already handles this correctly via its own per-frame shader update (see here). This PR brings ScatterGL in line with the same pattern.

To reproduce this, you can run the following in a notebook:

import numpy as np, ipywidgets as widgets
from bqplot import Figure, Scatter, ScatterGL, Lines
from bqplot_image_gl import LinesGL
from bqplot_linlog import LinLogScale, LinLogAxis

rng = np.random.default_rng(0)
x = np.logspace(0, 2, 400)
y = x * np.exp(0.25 * rng.standard_normal(x.size))

x_scale = LinLogScale(); y_scale = LinLogScale()
scales = {'x': x_scale, 'y': y_scale}

def make(MarkCls, title):
    m = MarkCls(x=x, y=y, scales=scales)
    return Figure(marks=[m], title=title,
                  axes=[LinLogAxis(scale=x_scale, label='x'),
                        LinLogAxis(scale=y_scale, orientation='vertical', label='y')],
                  layout=widgets.Layout(width='420px', height='320px'))

grid = widgets.GridBox([make(Scatter,'Scatter'), make(ScatterGL,'ScatterGL'),
                        make(Lines,'Lines'),     make(LinesGL,'LinesGL')],
                       layout=widgets.Layout(grid_template_columns='repeat(2, 440px)'))
toggle = widgets.ToggleButtons(options=['linear','log'], value='linear')
def flip(c): x_scale.mode = y_scale.mode = c['new']
toggle.observe(flip, names='value')
widgets.VBox([toggle, grid])

Flip the toggle to log. Without the fix, ScatterGL visibly diverges from Scatter:
Screenshot 2026-04-23 at 11 57 15

With the fix, they stay identical:

Screenshot 2026-04-23 at 11 57 40

I opened this to 0.12.x since the code no longer exists in master - if this is accepted I can open a similar PR to bqplot-gl, but wanted to also fix it here since it is a bug which is affecting us.

`customLauncher` was a typo for `customLaunchers`, so the custom flags
were silently ignored and karma used the built-in ChromeHeadless
defaults. That was tolerable on older runners but broke once CI moved
to ubuntu-24.04:

- ubuntu-24.04's AppArmor user-namespace restriction prevents chromium
  from starting without --no-sandbox
- chromium 132+ requires an explicit --enable-unsafe-swiftshader opt-in
  to fall back to software WebGL when --disable-gpu is in effect (which
  the ChromeHeadless base adds automatically)
@astrofrog astrofrog force-pushed the fix-scattergl-scale-update branch from 7343af1 to 0396513 Compare April 23, 2026 12:33
@astrofrog
Copy link
Copy Markdown
Contributor Author

I had to fix the CI, as it was failing due to stale config - this should now be ready for review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant