[charts] Stabilize WebGL bar rendering at sub-pixel widths#22678
Conversation
Adds three knobs to the WebGL bar data hook so the renderer survives dense data and aggressive zoom-out without producing the moire, dark hairlines, and accordion oscillation the per-bar emission used to ship at sub-pixel widths: - GAP_FILL_THRESHOLD_PX: when the natural barGapRatio gap drops below one pixel, expand each bar's band-axis half-size to half the pitch so adjacent quads meet exactly. Kills the dark hairlines the rasterizer leaves when pixel centers land in the gap. - MIN_BAND_HALF_SIZE_PX: floor on the rendered band-axis half-size so quads can't fall between two pixel centers and get culled by the rasterizer at extreme zoom-out. - Math.round on the probed bar size so the rendered half-size stays stable across zoom steps instead of oscillating around its target. The pitch is derived from the first two visible bars in the series and divided by their dataIndex gap so a null bar between the probes doesn't inflate the pitch to two bandwidths. Hidden state is series-level (mirrored onto every bar by useBarPlotData) so the per-bar hidden check moves to a single peek that skips the whole series.
Deploy previewBundle size
PerformanceTotal duration: 1,867.43 ms +129.23 ms(+7.4%) | Renders: 67 (+0) | Paint: 2,671.71 ms +161.53 ms(+6.4%) No significant changes — details Check out the code infra dashboard for more information about this PR. |
| // Consider the natural barGapRatio gap to be zero when it's below the | ||
| // threshold. The bar's rendered band-axis half-size is padded out to | ||
| // half the pitch so adjacent quads meet exactly, killing the dark | ||
| // hairlines the rasterizer leaves where pixel centers land in the gap. | ||
| // The MIN_BAND_HALF_SIZE_PX floor keeps very thin bars from being | ||
| // dropped by the rasterizer at extreme zoom-out. | ||
| const gap = pitch - barSize; | ||
| const fillGap = gap > 0 && gap < GAP_FILL_THRESHOLD_PX; | ||
| let bandHalfRender = fillGap ? pitch * 0.5 : barSize * 0.5; | ||
| if (bandHalfRender < MIN_BAND_HALF_SIZE_PX) { | ||
| bandHalfRender = MIN_BAND_HALF_SIZE_PX; | ||
| } |
There was a problem hiding this comment.
Took me some time to get where the issue was
When you zoom, there is a small range where the renderer osciliate between "fill gap" and "do not fill gap"
It's not a bug, it's because the rounding of the bar width create discontinuities

A solution can be to pick the linear lower bound of the gap which is const gapLowerBound = pitch - rawBarSize - 0.5;
Yes it's just removing 0.5 😅
In this screen shot,
By the way what you call pitch is a step in d3
| // Floor to keep thin quads from falling between pixel centers and getting | ||
| // culled at extreme zoom-out. |
There was a problem hiding this comment.
To clarify this 0.5 does not come from no-where. It comes from x-0.5<round(x)<x+0.5
If we used floor or ceil to approximate the bar size we should pick 0, or -1
Follow-up to #22607. Splits the sub-pixel rendering fixes out of the range-bar PR so the renderer changes can land on their own.
Problem
The WebGL bar renderer emits one rect per bar at the SVG-coord positions. At aggressive zoom-out the bars become sub-pixel:
Fix
Three knobs in `useWebGLBarLikePlotData`:
The pitch is derived from the first two visible bars in the series and divided by their `dataIndex` gap, so a null bar between the probes doesn't inflate the pitch to two bandwidths.
`hidden` is series-level (mirrored onto every bar by `useBarPlotData`), so the per-bar hidden check moves to a single peek up front.