Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/LiveChartsCore/Measure/VectorManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

using System;
using System.Collections.Generic;
using LiveChartsCore.Drawing.Segments;

namespace LiveChartsCore.Measure;
Expand All @@ -35,6 +33,13 @@ public void AddConsecutiveSegment(Segment segment, bool followsPrevious)
LinkedListNode<Segment>? replaceCandidate = null;
List<LinkedListNode<Segment>>? deleteCandidates = null;

Comment on lines 23 to 35
if (_currentNode is not null && segment.Id < _currentNode.Value.Id)
{
// this is a special case, normally caused because the gaps changed (null points).
// restart from the beginning to keep the linked list in sync.
_currentNode = list.First;
}

// look for the segment in the list
while (_currentNode is not null && _currentNode.Value != segment)
{
Expand Down Expand Up @@ -64,14 +69,15 @@ public void AddConsecutiveSegment(Segment segment, bool followsPrevious)
// at this point we know that the path contains this segment
// but the instance changed, so we replace the node

if (replaceCandidate is null)
throw new InvalidOperationException("This should not happen :(");
if (replaceCandidate is not null)
{
if (followsPrevious)
segment.Copy(replaceCandidate.Value);

if (followsPrevious)
segment.Copy(replaceCandidate.Value);
replaceCandidate.Value = segment;
}

replaceCandidate.Value = segment;
_currentNode = replaceCandidate.Next;
_currentNode = replaceCandidate?.Next;
}
else
{
Expand Down
260 changes: 259 additions & 1 deletion tests/SnapshotTests/LineSeriesTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using LiveChartsCore.Defaults;
using System.Collections.ObjectModel;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.SKCharts;
Expand Down Expand Up @@ -85,4 +85,262 @@ public void Curved()

chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(Curved)}");
}

[TestMethod]
public void VectorOperationsRemoveEnd()
{
var values = new ObservableCollection<int>([1, 2, 3, 4, 5]);

var chart = new SKCartesianChart
{
Series = [
new LineSeries<int>
{
Values = values,
Fill = null
}
],
Width = 600,
Height = 600
};

// draw the chart
_ = chart.GetImage();

// do the change, redraw and assert
values.RemoveAt(values.Count - 1);
chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(VectorOperationsRemoveEnd)}");
}

[TestMethod]
public void VectorOperationsRemoveStart()
{
var values = new ObservableCollection<int>([1, 2, 3, 4, 5]);
var chart = new SKCartesianChart
{
Series = [
new LineSeries<int>
{
Values = values,
Fill = null
}
],
Width = 600,
Height = 600
};
// draw the chart
_ = chart.GetImage();
// do the change, redraw and assert
values.RemoveAt(0);
chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(VectorOperationsRemoveStart)}");
}

[TestMethod]
public void VectorOperationsRemoveMiddle()
{
var values = new ObservableCollection<int>([1, 2, 3, 4, 5]);
var chart = new SKCartesianChart
{
Series = [
new LineSeries<int>
{
Values = values,
Fill = null
}
],
Width = 600,
Height = 600
};
// draw the chart
_ = chart.GetImage();

// do the change, redraw and assert
values.RemoveAt(2);
chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(VectorOperationsRemoveMiddle)}");
}

[TestMethod]
public void VectorOperationsAdd()
{
var values = new ObservableCollection<int>([1, 2, 3, 4, 5]);
var chart = new SKCartesianChart
{
Series = [
new LineSeries<int>
{
Values = values,
Fill = null
}
],
Width = 600,
Height = 600
};
// draw the chart
_ = chart.GetImage();

// do the change, redraw and assert
values.Add(6);
chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(VectorOperationsAdd)}");
}

[TestMethod]
public void VectorOperationsInsertMiddle()
{
var values = new ObservableCollection<int>([1, 2, 3, 4, 5]);

var chart = new SKCartesianChart
{
Series = [
new LineSeries<int>
{
Values = values,
Fill = null
}
],
Width = 600,
Height = 600
};

// draw the chart
_ = chart.GetImage();

// do the change, redraw and assert
values.Insert(2, 6);
chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(VectorOperationsInsertMiddle)}");
}

[TestMethod]
public void VectorOperationsInsertStart()
{
var values = new ObservableCollection<int>([1, 2, 3, 4, 5]);
var chart = new SKCartesianChart
{
Series = [
new LineSeries<int>
{
Values = values,
Fill = null
}
],
Width = 600,
Height = 600
};
// draw the chart
_ = chart.GetImage();
// do the change, redraw and assert
values.Insert(0, 6);
chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(VectorOperationsInsertStart)}");
}

[TestMethod]
public void VectorOperationsInsertEnd()
{
var values = new ObservableCollection<int>([1, 2, 3, 4, 5]);
var chart = new SKCartesianChart
{
Series = [
new LineSeries<int>
{
Values = values,
Fill = null
}
],
Width = 600,
Height = 600
};
// draw the chart
_ = chart.GetImage();
// do the change, redraw and assert
values.Insert(values.Count, 6);
chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(VectorOperationsInsertEnd)}");
}

[TestMethod]
public void VectorOperationsReplace()
{
var values = new ObservableCollection<int>([1, 2, 3, 4, 5]);

var chart = new SKCartesianChart
{
Series = [
new LineSeries<int>
{
Values = values,
Fill = null
}
],
Width = 600,
Height = 600
};

// draw the chart
_ = chart.GetImage();

// do the change, redraw and assert
values[2] = 6;
chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(VectorOperationsReplace)}");
}

[TestMethod]
public void VectorOperationsClear()
{
var values = new ObservableCollection<int>([1, 2, 3, 4, 5]);
var chart = new SKCartesianChart
{
Series = [
new LineSeries<int>
{
Values = values,
Fill = null
}
],
Width = 600,
Height = 600
};
// draw the chart
_ = chart.GetImage();
// do the change, redraw and assert
values.Clear();
chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(VectorOperationsClear)}");
}


[TestMethod]
public void Gaps()
{
var points = new ObservableCollection<int?>([1, 1]);

var chart = new SKCartesianChart
{
Series = [
new LineSeries<int?>
{
Values = points,
Fill = null
}
],
Width = 600,
Height = 600
};

_ = chart.GetImage();

var count = 0;
int?[] toAdd = [null, 1];

void Push()
{
points.Add(toAdd[count++ % toAdd.Length]);
points.RemoveAt(0);

_ = chart.GetImage();
}
Comment on lines +326 to +337
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SKCartesianChart.GetImage() returns an SKImage that holds unmanaged resources and should be disposed. These calls discard the returned image without disposing it, which can leak native memory during the test run. Consider disposing the returned image (or switching to SaveImage, which already disposes internally) for the warm-up render and for each Push() render.

Copilot uses AI. Check for mistakes.

Push();
Push();
Push();
Push();

chart.AssertSnapshotMatches($"{nameof(LineSeriesTests)}_{nameof(Gaps)}");
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading