Skip to content

Commit 280a116

Browse files
federicocoppolaobjorke
authored andcommitted
Support discontinuous area series #215
1 parent eeda3f5 commit 280a116

4 files changed

Lines changed: 195 additions & 72 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ All notable changes to this project will be documented in this file.
2525
- Support for a Xamarin Forms UWP project with sample app. Nuget package will be added when Xamarin Forms UWP is released outside preview. (#697)
2626
- Add ListBuilder for building lists by reflection (#705)
2727
- F# example (#699)
28+
- Support for discontinuities in AreaSeries (#215)
2829

2930
### Changed
3031
- Renamed OxyPlot.WindowsUniversal to OxyPlot.Windows (#242)

CONTRIBUTORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ efontana2
3434
elliatab
3535
episage <tilosag@gmail.com>
3636
eric
37+
Federico Coppola <fede@silentman.it>
3738
Francois Botha <igitur@gmail.com>
3839
Garrett
3940
Geert van Horrik <geert@catenalogic.com>

Source/Examples/ExampleLibrary/Examples/FilteringExamples.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,46 @@ public static PlotModel FilteringInvalidPoints()
4141
return plot;
4242
}
4343

44+
[Example("Filtering NaN points with AreaSeries")]
45+
public static PlotModel FilteringInvalidPointsAreaSeries()
46+
{
47+
var plot = new PlotModel { Title = "Filtering NaN points in an AreaSeries" };
48+
plot.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom });
49+
plot.Axes.Add(new LinearAxis { Position = AxisPosition.Left });
50+
51+
var as1 = new AreaSeries();
52+
as1.Points.Add(new DataPoint(1, 0));
53+
as1.Points.Add(new DataPoint(2, 10));
54+
as1.Points.Add(new DataPoint(3, 10));
55+
as1.Points.Add(new DataPoint(4, 0));
56+
as1.Points.Add(new DataPoint(5, 0));
57+
as1.Points.Add(new DataPoint(6, 7));
58+
as1.Points.Add(new DataPoint(7, 7));
59+
as1.Points.Add(new DataPoint(double.NaN, double.NaN));
60+
as1.Points.Add(new DataPoint(double.NaN, double.NaN));
61+
as1.Points.Add(new DataPoint(8, 0));
62+
as1.Points.Add(new DataPoint(9, 0));
63+
as1.Points.Add(new DataPoint(double.NaN, double.NaN));
64+
65+
as1.Points2.Add(new DataPoint(1, 10));
66+
as1.Points2.Add(new DataPoint(2, 110));
67+
as1.Points2.Add(new DataPoint(3, 110));
68+
as1.Points2.Add(new DataPoint(4, 10));
69+
as1.Points2.Add(new DataPoint(5, 10));
70+
as1.Points2.Add(new DataPoint(6, 17));
71+
as1.Points2.Add(new DataPoint(7, 17));
72+
as1.Points2.Add(new DataPoint(double.NaN, double.NaN));
73+
as1.Points2.Add(new DataPoint(double.NaN, double.NaN));
74+
as1.Points2.Add(new DataPoint(8, 10));
75+
as1.Points2.Add(new DataPoint(9, 10));
76+
as1.Points2.Add(new DataPoint(double.NaN, double.NaN));
77+
78+
plot.Series.Add(as1);
79+
80+
return plot;
81+
}
82+
83+
4484
[Example("Filtering undefined points")]
4585
public static PlotModel FilteringUndefinedPoints()
4686
{

Source/OxyPlot/Series/AreaSeries.cs

Lines changed: 153 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99

1010
namespace OxyPlot.Series
1111
{
12-
using System.Linq;
12+
using System;
1313
using System.Collections.Generic;
14+
using System.Linq;
1415

1516
/// <summary>
1617
/// Represents an area series that fills the polygon defined by two sets of points or one set of points and a constant.
@@ -189,10 +190,9 @@ public override TrackerHitResult GetNearestPoint(ScreenPoint point, bool interpo
189190
/// <param name="rc">The rendering context.</param>
190191
public override void Render(IRenderContext rc)
191192
{
192-
var dataPoints1 = this.ActualPoints;
193-
var dataPoints2 = this.ActualPoints2;
194-
int n0 = dataPoints1.Count;
195-
if (n0 == 0)
193+
var actualPoints = this.ActualPoints;
194+
var actualPoints2 = this.ActualPoints2;
195+
if (actualPoints.Count == 0)
196196
{
197197
return;
198198
}
@@ -204,83 +204,146 @@ public override void Render(IRenderContext rc)
204204
var clippingRect = this.GetClippingRect();
205205
rc.SetClip(clippingRect);
206206

207-
// Transform all points to screen coordinates
208-
IList<ScreenPoint> pts0 = new ScreenPoint[n0];
209-
for (int i = 0; i < n0; i++)
207+
// Manage NaN's
208+
var chunkedListsOfPoints = this.Split(actualPoints, p => double.IsNaN(p.Y));
209+
var chunkedListsOfPoints2 = this.Split(actualPoints2, p => double.IsNaN(p.Y));
210+
211+
for (int chunkIndex = 0; chunkIndex < chunkedListsOfPoints.Count(); chunkIndex++)
210212
{
211-
pts0[i] = this.XAxis.Transform(dataPoints1[i].X, dataPoints1[i].Y, this.YAxis);
213+
var chunkActualPoints = chunkedListsOfPoints.ElementAt(chunkIndex).ToList();
214+
215+
// Transform all points to screen coordinates
216+
int n0 = chunkActualPoints.Count;
217+
IList<ScreenPoint> pts0 = new ScreenPoint[n0];
218+
for (int i = 0; i < n0; i++)
219+
{
220+
pts0[i] = this.XAxis.Transform(chunkActualPoints[i].X, chunkActualPoints[i].Y, this.YAxis);
221+
}
222+
223+
if (this.Smooth)
224+
{
225+
var rpts0 = ScreenPointHelper.ResamplePoints(pts0, this.MinimumSegmentLength);
226+
pts0 = CanonicalSplineHelper.CreateSpline(rpts0, 0.5, null, false, 0.25);
227+
}
228+
229+
var dashArray = this.ActualDashArray;
230+
231+
// draw the clipped lines
232+
rc.DrawClippedLine(
233+
clippingRect,
234+
pts0,
235+
minDistSquared,
236+
this.GetSelectableColor(this.ActualColor),
237+
this.StrokeThickness,
238+
dashArray,
239+
this.LineJoin,
240+
false);
212241
}
213242

214-
int n1 = dataPoints2.Count;
215-
IList<ScreenPoint> pts1 = new ScreenPoint[n1];
216-
for (int i = 0; i < n1; i++)
243+
for (int chunkIndex = 0; chunkIndex < chunkedListsOfPoints2.Count(); chunkIndex++)
217244
{
218-
int j = this.Reverse2 ? n1 - 1 - i : i;
219-
pts1[j] = this.XAxis.Transform(dataPoints2[i].X, dataPoints2[i].Y, this.YAxis);
245+
var chunkActualPoints2 = chunkedListsOfPoints2.ElementAt(chunkIndex).ToList();
246+
247+
// Transform all points to screen coordinates
248+
int n1 = chunkActualPoints2.Count;
249+
IList<ScreenPoint> pts1 = new ScreenPoint[n1];
250+
for (int i = 0; i < n1; i++)
251+
{
252+
int j = this.Reverse2 ? n1 - 1 - i : i;
253+
pts1[j] = this.XAxis.Transform(chunkActualPoints2[i].X, chunkActualPoints2[i].Y, this.YAxis);
254+
}
255+
256+
if (this.Smooth)
257+
{
258+
var rpts1 = ScreenPointHelper.ResamplePoints(pts1, this.MinimumSegmentLength);
259+
pts1 = CanonicalSplineHelper.CreateSpline(rpts1, 0.5, null, false, 0.25);
260+
}
261+
262+
var dashArray = this.ActualDashArray;
263+
264+
// draw the clipped lines
265+
rc.DrawClippedLine(
266+
clippingRect,
267+
pts1,
268+
minDistSquared,
269+
this.GetSelectableColor(this.ActualColor2),
270+
this.StrokeThickness,
271+
dashArray,
272+
this.LineJoin,
273+
false);
220274
}
221275

222-
if (this.Smooth)
276+
if (chunkedListsOfPoints.Count() != chunkedListsOfPoints2.Count())
223277
{
224-
var rpts0 = ScreenPointHelper.ResamplePoints(pts0, this.MinimumSegmentLength);
225-
var rpts1 = ScreenPointHelper.ResamplePoints(pts1, this.MinimumSegmentLength);
226-
227-
pts0 = CanonicalSplineHelper.CreateSpline(rpts0, 0.5, null, false, 0.25);
228-
pts1 = CanonicalSplineHelper.CreateSpline(rpts1, 0.5, null, false, 0.25);
278+
rc.ResetClip();
279+
return;
229280
}
230281

231-
var dashArray = this.ActualDashArray;
232-
233-
// draw the clipped lines
234-
rc.DrawClippedLine(
235-
clippingRect,
236-
pts0,
237-
minDistSquared,
238-
this.GetSelectableColor(this.ActualColor),
239-
this.StrokeThickness,
240-
dashArray,
241-
this.LineJoin,
242-
false);
243-
rc.DrawClippedLine(
244-
clippingRect,
245-
pts1,
246-
minDistSquared,
247-
this.GetSelectableColor(this.ActualColor2),
248-
this.StrokeThickness,
249-
dashArray,
250-
this.LineJoin,
251-
false);
252-
253-
// combine the two lines and draw the clipped area
254-
var pts = new List<ScreenPoint>();
255-
pts.AddRange(pts1);
256-
pts.AddRange(pts0);
282+
// Draw the fill
283+
for (int chunkIndex = 0; chunkIndex < chunkedListsOfPoints.Count(); chunkIndex++)
284+
{
285+
var chunkActualPoints = chunkedListsOfPoints.ElementAt(chunkIndex).ToList();
286+
var chunkActualPoints2 = chunkedListsOfPoints2.ElementAt(chunkIndex).ToList();
257287

258-
// pts = SutherlandHodgmanClipping.ClipPolygon(clippingRect, pts);
259-
rc.DrawClippedPolygon(clippingRect, pts, minDistSquared, this.GetSelectableFillColor(this.ActualFill), OxyColors.Undefined);
260-
261-
var markerSizes = new[] { this.MarkerSize };
262-
263-
// draw the markers on top
264-
rc.DrawMarkers(
265-
clippingRect,
266-
pts0,
267-
this.MarkerType,
268-
null,
269-
markerSizes,
270-
this.MarkerFill,
271-
this.MarkerStroke,
272-
this.MarkerStrokeThickness,
273-
1);
274-
rc.DrawMarkers(
275-
clippingRect,
276-
pts1,
277-
this.MarkerType,
278-
null,
279-
markerSizes,
280-
this.MarkerFill,
281-
this.MarkerStroke,
282-
this.MarkerStrokeThickness,
283-
1);
288+
// Transform all points to screen coordinates
289+
int n0 = chunkActualPoints.Count;
290+
IList<ScreenPoint> pts0 = new ScreenPoint[n0];
291+
for (int i = 0; i < n0; i++)
292+
{
293+
pts0[i] = this.XAxis.Transform(chunkActualPoints[i].X, chunkActualPoints[i].Y, this.YAxis);
294+
}
295+
296+
int n1 = chunkActualPoints2.Count;
297+
IList<ScreenPoint> pts1 = new ScreenPoint[n1];
298+
for (int i = 0; i < n1; i++)
299+
{
300+
int j = this.Reverse2 ? n1 - 1 - i : i;
301+
pts1[j] = this.XAxis.Transform(chunkActualPoints2[i].X, chunkActualPoints2[i].Y, this.YAxis);
302+
}
303+
304+
if (this.Smooth)
305+
{
306+
var rpts0 = ScreenPointHelper.ResamplePoints(pts0, this.MinimumSegmentLength);
307+
var rpts1 = ScreenPointHelper.ResamplePoints(pts1, this.MinimumSegmentLength);
308+
309+
pts0 = CanonicalSplineHelper.CreateSpline(rpts0, 0.5, null, false, 0.25);
310+
pts1 = CanonicalSplineHelper.CreateSpline(rpts1, 0.5, null, false, 0.25);
311+
}
312+
313+
var dashArray = this.ActualDashArray;
314+
315+
// combine the two lines and draw the clipped area
316+
var pts = new List<ScreenPoint>();
317+
pts.AddRange(pts1);
318+
pts.AddRange(pts0);
319+
320+
// pts = SutherlandHodgmanClipping.ClipPolygon(clippingRect, pts);
321+
rc.DrawClippedPolygon(clippingRect, pts, minDistSquared, this.GetSelectableFillColor(this.ActualFill), OxyColors.Undefined);
322+
323+
var markerSizes = new[] { this.MarkerSize };
324+
325+
// draw the markers on top
326+
rc.DrawMarkers(
327+
clippingRect,
328+
pts0,
329+
this.MarkerType,
330+
null,
331+
markerSizes,
332+
this.MarkerFill,
333+
this.MarkerStroke,
334+
this.MarkerStrokeThickness,
335+
1);
336+
rc.DrawMarkers(
337+
clippingRect,
338+
pts1,
339+
this.MarkerType,
340+
null,
341+
markerSizes,
342+
this.MarkerFill,
343+
this.MarkerStroke,
344+
this.MarkerStrokeThickness,
345+
1);
346+
}
284347

285348
rc.ResetClip();
286349
}
@@ -366,5 +429,23 @@ private IEnumerable<DataPoint> GetConstantPoints2()
366429
yield return new DataPoint(x1, this.ConstantY2);
367430
}
368431
}
432+
433+
/// <summary>
434+
/// Split an IEnumerable<typeparamref name="T"/> into chunks (sub-lists), based on a split condition
435+
/// (input items with splitCondition == true will not be included in output)
436+
/// </summary>
437+
/// <typeparam name="T">The type of the input list item</typeparam>
438+
/// <param name="source">The input list</param>
439+
/// <param name="splitCondition">The split condition</param>
440+
/// <returns>A collection of a collection of <typeparamref name="T"/> items</returns>
441+
private IEnumerable<IEnumerable<T>> Split<T>(IEnumerable<T> source, Func<T, bool> splitCondition)
442+
{
443+
source = source.SkipWhile(splitCondition);
444+
while (source.Any())
445+
{
446+
yield return source.TakeWhile(x => !splitCondition(x));
447+
source = source.SkipWhile(x => !splitCondition(x)).SkipWhile(splitCondition);
448+
}
449+
}
369450
}
370451
}

0 commit comments

Comments
 (0)