diff --git a/src/LiveChartsCore/CoreLineSeries.cs b/src/LiveChartsCore/CoreLineSeries.cs index 4eec2c82f..9a3f31e92 100644 --- a/src/LiveChartsCore/CoreLineSeries.cs +++ b/src/LiveChartsCore/CoreLineSeries.cs @@ -20,9 +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 System.Linq; using LiveChartsCore.Drawing; using LiveChartsCore.Drawing.Segments; using LiveChartsCore.Kernel; @@ -798,6 +795,14 @@ protected internal override void SoftDeleteOrDisposePoint(ChartPoint point, Scal visual.XError.RemoveOnCompleted = true; } + foreach (var pathCollection in _strokePathHelperDictionary.Values) + foreach (var path in pathCollection) + _ = path.Commands.Remove(visual.Segment); + + foreach (var pathCollection in _fillPathHelperDictionary.Values) + foreach (var path in pathCollection) + _ = path.Commands.Remove(visual.Segment); + DataFactory.DisposePoint(point); var label = (TLabel?)point.Context.Label; diff --git a/src/LiveChartsCore/CoreStepLineSeries.cs b/src/LiveChartsCore/CoreStepLineSeries.cs index 1e118d3e3..58b7eba05 100644 --- a/src/LiveChartsCore/CoreStepLineSeries.cs +++ b/src/LiveChartsCore/CoreStepLineSeries.cs @@ -50,8 +50,8 @@ public abstract class CoreStepLineSeries> _fillPathHelperDictionary = []; - private readonly Dictionary> _strokePathHelperDictionary = []; + internal readonly Dictionary> _fillPathHelperDictionary = []; + internal readonly Dictionary> _strokePathHelperDictionary = []; private float _geometrySize = 14f; /// @@ -553,6 +553,14 @@ protected internal override void SoftDeleteOrDisposePoint(ChartPoint point, Scal visual.Geometry.Opacity = 0; visual.Geometry.RemoveOnCompleted = true; + foreach (var pathCollection in _strokePathHelperDictionary.Values) + foreach (var path in pathCollection) + _ = path.Commands.Remove(visual.Segment); + + foreach (var pathCollection in _fillPathHelperDictionary.Values) + foreach (var path in pathCollection) + _ = path.Commands.Remove(visual.Segment); + DataFactory.DisposePoint(point); var label = (TLabel?)point.Context.Label; diff --git a/tests/CoreTests/SeriesTests/LineSeriesTest.cs b/tests/CoreTests/SeriesTests/LineSeriesTest.cs index 2eb52a831..f95ce9f82 100644 --- a/tests/CoreTests/SeriesTests/LineSeriesTest.cs +++ b/tests/CoreTests/SeriesTests/LineSeriesTest.cs @@ -1,14 +1,12 @@ -using System; -using System.Linq; -using CoreTests.MockedObjects; +using CoreTests.MockedObjects; using LiveChartsCore.Drawing; using LiveChartsCore.Drawing.Segments; +using LiveChartsCore.Kernel; using LiveChartsCore.Measure; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Drawing.Geometries; using LiveChartsCore.SkiaSharpView.Painting; using LiveChartsCore.SkiaSharpView.SKCharts; -using Microsoft.VisualStudio.TestTools.UnitTesting; using SkiaSharp; namespace CoreTests.SeriesTests; @@ -422,4 +420,116 @@ public void ShouldPlaceDataLabel() Assert.IsTrue(!chart.CoreCanvas.ContainsPaintTask(previousPaint)); } + + [TestMethod] + public void ShouldScaleBeziersOnAddRemoveOrInsert() + { + var values = new List { 1, 2, 3, 4, 5 }; + + var series = new LineSeries + { + Values = values, + GeometrySize = 10 + }; + + var chart = new SKCartesianChart + { + Width = 1000, + Height = 1000, + Series = [series] + }; + + void AssertIsStraightLine(ChartPoint[] points) + { + Assert.IsTrue(points.Length > 0); + + CubicBezierSegment? previous = null; + var slope = 0f; + + foreach (var p in points) + { + var tp = series.ConvertToTypedChartPoint(p); + var segment = (CubicSegmentVisualPoint?)tp.Context.AdditionalVisuals; + + Assert.IsNotNull(segment); + + if (previous is null) + { + previous = segment?.Segment; + continue; + } + + if (slope == 0f) + { + var dx = segment!.Segment.Xj - previous.Xj; + var dy = segment.Segment.Yj - previous.Yj; + slope = dx / dy; + } + + var currentSlope = (segment!.Segment.Xj - previous.Xj) / (segment.Segment.Yj - previous.Yj); + + Assert.IsTrue(Math.Abs(slope - currentSlope) < 0.1); + previous = segment.Segment; + } + } + + ChartPoint[] points; + int segments; + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 5); + + // if a the first point is removed, it should still be a straight line + values.RemoveAt(0); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 4); + + // if we insert a point at the start that keeps the straight line, it should be a straight line + values.Insert(0, 1); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 5); + + // if the last point is removed, it should still be a straight line + values.RemoveAt(values.Count - 1); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 4); + + // if we add a point that keeps the straight line, it should be a straight line + values.Add(5); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 5); + + // if we insert a point in the middle that keeps the straight line, it should be a straight line + values.RemoveAt(1); + _ = chart.GetImage(); + // at this point the values are [1,3,4,5] it is not a straight line + //AssertIsStraightLine([.. series.DataFactory.Fetch(series, chart.CoreChart)]); + + values.Insert(1, 2); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 5); + } } diff --git a/tests/CoreTests/SeriesTests/StepLineSeriesTest.cs b/tests/CoreTests/SeriesTests/StepLineSeriesTest.cs index 5279cde60..6bf3a7de7 100644 --- a/tests/CoreTests/SeriesTests/StepLineSeriesTest.cs +++ b/tests/CoreTests/SeriesTests/StepLineSeriesTest.cs @@ -1,14 +1,12 @@ -using System; -using System.Linq; -using CoreTests.MockedObjects; +using CoreTests.MockedObjects; using LiveChartsCore.Drawing; using LiveChartsCore.Drawing.Segments; +using LiveChartsCore.Kernel; using LiveChartsCore.Measure; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Drawing.Geometries; using LiveChartsCore.SkiaSharpView.Painting; using LiveChartsCore.SkiaSharpView.SKCharts; -using Microsoft.VisualStudio.TestTools.UnitTesting; using SkiaSharp; namespace CoreTests.SeriesTests; @@ -420,4 +418,116 @@ public void ShouldPlaceDataLabel() Assert.IsTrue(!chart.CoreCanvas.ContainsPaintTask(previousPaint)); } + + [TestMethod] + public void ShouldScaleBeziersOnAddRemoveOrInsert() + { + var values = new List { 1, 2, 3, 4, 5 }; + + var series = new StepLineSeries + { + Values = values, + GeometrySize = 10 + }; + + var chart = new SKCartesianChart + { + Width = 1000, + Height = 1000, + Series = [series] + }; + + void AssertIsStraightLine(ChartPoint[] points) + { + Assert.That(() => points.Length > 0); + + Segment? previous = null; + var slope = 0f; + + foreach (var p in points) + { + var tp = series.ConvertToTypedChartPoint(p); + var segment = (SegmentVisualPoint?)tp.Context.AdditionalVisuals; + + Assert.IsNotNull(segment); + + if (previous is null) + { + previous = segment?.Segment; + continue; + } + + if (slope == 0f) + { + var dx = segment!.Segment.Xj - previous.Xj; + var dy = segment.Segment.Yj - previous.Yj; + slope = dx / dy; + } + + var currentSlope = (segment!.Segment.Xj - previous.Xj) / (segment.Segment.Yj - previous.Yj); + + Assert.IsTrue(Math.Abs(slope - currentSlope) < 0.1); + previous = segment.Segment; + } + } + + ChartPoint[] points; + int segments; + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 5); + + // if a the first point is removed, it should still be a straight line + values.RemoveAt(0); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 4); + + // if we insert a point at the start that keeps the straight line, it should be a straight line + values.Insert(0, 1); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 5); + + // if the last point is removed, it should still be a straight line + values.RemoveAt(values.Count - 1); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 4); + + // if we add a point that keeps the straight line, it should be a straight line + values.Add(5); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 5); + + // if we insert a point in the middle that keeps the straight line, it should be a straight line + values.RemoveAt(1); + _ = chart.GetImage(); + // at this point the values are [1,3,4,5] it is not a straight line + //AssertIsStraightLine([.. series.DataFactory.Fetch(series, chart.CoreChart)]); + + values.Insert(1, 2); + _ = chart.GetImage(); + + points = [.. series.DataFactory.Fetch(series, chart.CoreChart)]; + segments = series._fillPathHelperDictionary.Sum(x => x.Value.Sum(y => y.Commands.Count)); + AssertIsStraightLine(points); + Assert.IsTrue(segments == 5); + } }