(seriesConfig, data, xScale, yScale, gridArea)
| 256 | }; |
| 257 | |
| 258 | const prepare: ScatterRenderer['prepare'] = (seriesConfig, data, xScale, yScale, gridArea) => { |
| 259 | assertNotDisposed(); |
| 260 | |
| 261 | const { xMin, xMax, yMin, yMax } = computeDataBounds(data); |
| 262 | const { a: ax, b: bx } = computeClipAffineFromScale(xScale, xMin, xMax); |
| 263 | const { a: ay, b: by } = computeClipAffineFromScale(yScale, yMin, yMax); |
| 264 | |
| 265 | if (gridArea) { |
| 266 | lastCanvasWidth = gridArea.canvasWidth; |
| 267 | lastCanvasHeight = gridArea.canvasHeight; |
| 268 | writeVsUniforms(ax, bx, ay, by, gridArea.canvasWidth, gridArea.canvasHeight); |
| 269 | lastScissor = computePlotScissorDevicePx(gridArea); |
| 270 | } else { |
| 271 | // Backward-compatible: keep rendering with the last known viewport (or safe default). |
| 272 | writeVsUniforms(ax, bx, ay, by, lastViewportPx[0], lastViewportPx[1]); |
| 273 | lastScissor = null; |
| 274 | } |
| 275 | |
| 276 | const [r, g, b, a] = parseSeriesColorToRgba01(seriesConfig.color); |
| 277 | fsUniformScratchF32[0] = r; |
| 278 | fsUniformScratchF32[1] = g; |
| 279 | fsUniformScratchF32[2] = b; |
| 280 | fsUniformScratchF32[3] = clamp01(a); |
| 281 | writeUniformBuffer(device, fsUniformBuffer, fsUniformScratchF32); |
| 282 | |
| 283 | const dpr = gridArea?.devicePixelRatio ?? 1; |
| 284 | const hasValidDpr = dpr > 0 && Number.isFinite(dpr); |
| 285 | |
| 286 | const seriesSymbolSize = seriesConfig.symbolSize; |
| 287 | const getSeriesSizeCssPx = |
| 288 | typeof seriesSymbolSize === 'function' |
| 289 | ? (point: DataPoint): number => { |
| 290 | const v = seriesSymbolSize(toScatterTuple(point)); |
| 291 | return typeof v === 'number' && Number.isFinite(v) ? v : DEFAULT_SCATTER_RADIUS_CSS_PX; |
| 292 | } |
| 293 | : typeof seriesSymbolSize === 'number' && Number.isFinite(seriesSymbolSize) |
| 294 | ? (): number => seriesSymbolSize |
| 295 | : (): number => DEFAULT_SCATTER_RADIUS_CSS_PX; |
| 296 | |
| 297 | ensureCpuInstanceCapacityFloats(data.length * INSTANCE_STRIDE_FLOATS); |
| 298 | const f32 = cpuInstanceStagingF32; |
| 299 | let outFloats = 0; |
| 300 | |
| 301 | for (let i = 0; i < data.length; i++) { |
| 302 | const p = data[i]; |
| 303 | const { x, y } = getPointXY(p); |
| 304 | if (!Number.isFinite(x) || !Number.isFinite(y)) continue; |
| 305 | |
| 306 | const sizeCss = getPointSizeCssPx(p) ?? getSeriesSizeCssPx(p); |
| 307 | const radiusCss = Number.isFinite(sizeCss) ? Math.max(0, sizeCss) : DEFAULT_SCATTER_RADIUS_CSS_PX; |
| 308 | const radiusDevicePx = hasValidDpr ? radiusCss * dpr : radiusCss; |
| 309 | if (!(radiusDevicePx > 0)) continue; |
| 310 | |
| 311 | f32[outFloats + 0] = x; |
| 312 | f32[outFloats + 1] = y; |
| 313 | f32[outFloats + 2] = radiusDevicePx; |
| 314 | f32[outFloats + 3] = 0; // pad |
| 315 | outFloats += INSTANCE_STRIDE_FLOATS; |
nothing calls this directly
no test coverage detected