()
| 3255 | }; |
| 3256 | |
| 3257 | const render: RenderCoordinator['render'] = () => { |
| 3258 | assertNotDisposed(); |
| 3259 | if (!gpuContext.canvasContext || !gpuContext.canvas) return; |
| 3260 | |
| 3261 | // Safety: if a render is triggered for other reasons (e.g. pointer movement) while appends |
| 3262 | // are queued, flush them now so this frame draws up-to-date data. This avoids doing any work |
| 3263 | // when there are no appends. |
| 3264 | if (pendingAppendByIndex.size > 0 || zoomResampleDue) { |
| 3265 | cancelScheduledFlush(); |
| 3266 | executeFlush({ requestRenderAfter: false }); |
| 3267 | } |
| 3268 | |
| 3269 | if (sliceRenderSeriesDue) { |
| 3270 | sliceRenderSeriesDue = false; |
| 3271 | sliceRenderSeriesToVisibleRange(); |
| 3272 | } |
| 3273 | |
| 3274 | const hasCartesianSeries = currentOptions.series.some((s) => s.type !== 'pie'); |
| 3275 | const seriesForIntro = renderSeries; |
| 3276 | |
| 3277 | // Story 5.16: start/update intro animation once we have drawable series marks. |
| 3278 | if (introPhase !== 'done') { |
| 3279 | const introCfg = resolveIntroAnimationConfig(currentOptions.animation); |
| 3280 | |
| 3281 | const hasDrawableSeriesMarks = (() => { |
| 3282 | for (let i = 0; i < seriesForIntro.length; i++) { |
| 3283 | const s = seriesForIntro[i]!; |
| 3284 | switch (s.type) { |
| 3285 | case 'pie': { |
| 3286 | // Pie renderer only emits slices with value > 0. |
| 3287 | if (s.data.some((it) => typeof it?.value === 'number' && Number.isFinite(it.value) && it.value > 0)) { |
| 3288 | return true; |
| 3289 | } |
| 3290 | break; |
| 3291 | } |
| 3292 | case 'line': |
| 3293 | case 'area': |
| 3294 | case 'bar': |
| 3295 | case 'scatter': |
| 3296 | case 'candlestick': { |
| 3297 | if (s.data.length > 0) return true; |
| 3298 | break; |
| 3299 | } |
| 3300 | default: |
| 3301 | assertUnreachable(s); |
| 3302 | } |
| 3303 | } |
| 3304 | return false; |
| 3305 | })(); |
| 3306 | |
| 3307 | if (introPhase === 'pending' && introCfg && hasDrawableSeriesMarks) { |
| 3308 | const totalMs = introCfg.delayMs + introCfg.durationMs; |
| 3309 | const easingWithDelay: EasingFunction = (t01) => { |
| 3310 | const t = clamp01(t01); |
| 3311 | if (!(totalMs > 0)) return 1; |
| 3312 | |
| 3313 | const elapsedMs = t * totalMs; |
| 3314 | if (elapsedMs <= introCfg.delayMs) return 0; |
nothing calls this directly
no test coverage detected