Skip to content

Chart: a frame can stroke a pen against a half-swapped axis pair #64

Description

@mrcsin

Symptom

A pen's line can be drawn for one frame with part of its points scaled through the old Y axis and part through the new one: a step in the line, or a stretch of it outside the data area, gone on the next frame. It shows as a flicker at the moment the axis assignment changes: a pen regrouped by unit, a manual/auto scale switch, a pen added.

Not reproduced by hand yet. A drag alone does not trigger it because the Y axes do not change during a drag.

Cause

ChartAxisBinder.AssignPensToAxis (SemiPlot/SemiPlot.UI/Chart/ChartAxisBinder.cs:70) writes state.Line.Axes.YAxis = axis on the UI thread on every ApplyAxisModel, which runs after each history apply and each window change.

EnvelopeLine.Render reads Axes on Avalonia's render thread outside any lock: Axes.XAxis.Min/Max for the visible range (EnvelopeLine.cs:82) and Axes.GetPixel per point (EnvelopeLine.cs:94). Axes is ScottPlot's plain object with two reference properties; the column-buffer lock from #59 does not cover it.

A reference write is atomic, so nothing throws. A frame is not atomic: a write landing between two GetPixel calls leaves that frame drawn against two axes.

Fix options

  1. Render snapshots Axes.XAxis and Axes.YAxis into locals once at the top and uses them for the whole frame. No lock, no UI-thread blocking; one frame sees one consistent pair. Smallest change.
  2. Route the assignment through a method on EnvelopeLine that takes the existing _columnsLock, and read Axes under the same lock in Render. Consistent with Chart crashes on drag: EnvelopeLine renders a pen's column list while the UI thread rewrites it #59 but blocks the UI thread for nothing the snapshot does not already give.

Option 1 is enough.

Related

TrendChartViewModel.RemovePen (TrendChartViewModel.cs:248) calls Plot.Remove(state.Line), which mutates ScottPlot's PlottableList while the render thread enumerates it with foreach: InvalidOperationException (collection modified) if it ever runs during a frame. Latent today because RemovePen has no production caller and AddPen runs once at startup before the window is shown. Decide with the behaviour/UX round: delete the method with its tests, or reimplement pen removal through IsVisible rather than Plot.Remove.

Provenance

Found during the review of #63. Present on master since the culled polyline plottable (013f491, #57).

Activity

  1. self-assigned this
    on Sep 10, 2026
  2. mrcsin commented on Oct 7, 2026

    @mrcsin
    MemberAuthor

    Obsolete on master (823c687). EnvelopeLine.Render reads Axes.YAxis into a local once per frame (EnvelopeLine.cs:86), the fix this issue proposed; it landed with #113. Each pen also keeps one Y axis for its lifetime (ChartAxisBinder.ResolveAxis), so the assignment at ChartAxisBinder.cs:41 rewrites the same reference and no swap occurs. The related TrendChartViewModel.RemovePen and its Plot.Remove call are gone; a removed pen's axis is hidden through HideAxis.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions