Repository navigation
perf(chart): redraw per display frame and cache the plot - #117
Merged
Merged
Conversation
A drag repainted the chart at about 30 frames per second and late (plot frame spacing p50 33.4 ms, pan step to the next frame p50 24.4 ms), a hover over a still plot re-rendered it about 52 times per second, and every hidden pen axis regenerated its ticks twice per frame. No thread was saturated; the costs that scale with the pen count left little headroom for a 50-pen catalogue. The redraw is paced by the display: the view model raises its redraw signal at once and the view coalesces requests into one TopLevel.RequestAnimationFrame, whose callback invalidates the plot. The 33 ms timer and ChartRedrawSchedule are gone; RequestRedraw is UI thread only, which Avalonia enforces. The plot sits alone inside a decorator that carries a BitmapCache, so a pointer move repaints the overlay over a cached raster and ScottPlot renders only when the plot changes. The cache is on the wrapper because Avalonia 12 never refreshes a BitmapCache set on a self-drawing visual such as AvaPlot. LinearTickGenerator and LogTickGenerator generate nothing while their axis is hidden and keep their last two results, since ScottPlot asks for each axis at two lengths per frame; ChartAxisBinder flips IsDrawn and IsVisible together under Plot.Sync, and only when they change. The history debouncer admits a request on the data scheduler after the Sample and Throttle gates, so a UI-thread request never waits on a starting query, and its Dispose completes rather than disposes the admission subject. The chart posts its data-area width only when it changes. trace-shares.py reports frame spacing, pan latency, compositor rate and tick cost, after an optional warm-up skip. Measured on the demo stand at 8 pens: drag frame spacing p50 17.8 ms, hover about one render per second, follow mode one render per data tick.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A drag repainted the chart at about 30 frames per second and late (plot frame spacing p50 33.4 ms, pan step to
the next frame p50 24.4 ms, 2026-10-08, 8 pens), a hover over a still plot re-rendered it about 52 times per
second, and every hidden pen axis regenerated its ticks twice per frame. No thread was saturated; the costs that
scale with the pen count left little headroom for a 50-pen catalogue.
Change
RedrawRequestedat once; the view coalesces requests into oneTopLevel.RequestAnimationFrame, whose callback callsPlotControl.InvalidateVisual().ChartRedrawScheduleand its 33 ms timer are deleted;
RequestRedrawis UI-thread only, enforced by Avalonia.PlotControlsits alone inside thePlotLayerdecorator, which carries aBitmapCache; the overlay repaints over the cached raster. The cache is on the wrapper because Avalonia 12 neverrefreshes a
BitmapCacheset on a self-drawing visual such asAvaPlot(charting.md#hover-and-the-plot-cache).LinearTickGeneratorandLogTickGeneratorgenerate nothing while hidden and keeptheir last two results (
TickCache);ChartAxisBinderflipsIsDrawnandIsVisibletogether underPlot.Sync, only on a change. With the active pen switched off the plot shows no horizontal gridlines.Sample/Throttlegates;Disposecompletes theadmission subject instead of disposing it.
scripts/perf/trace-shares.pyreports frame spacing, pan latency, compositor rate and tick cost, with--skip-seconds;testing-strategy.md#frame-coststates the stand checks, including the follow capture thatcatches a plot that stops redrawing.
Verification
dotnet test SemiPlot.slnx: unit 1843, integration 147;dotnet format --verify-no-changesclean.per data tick; hover over a still plot about one render per second; theme switch repaints without a resize.
Per-task commits before collapse