Skip to content

Commit

Permalink
Plot.PlotFill() #255
Browse files Browse the repository at this point in the history
  • Loading branch information
swharden committed Apr 8, 2020
1 parent 9a655fc commit 042e582
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 0 deletions.
1 change: 1 addition & 0 deletions dev/changelog.md
Expand Up @@ -26,6 +26,7 @@ _ScottPlot uses [semantic](https://semver.org/) (major.minor.patch) versioning.
* Create a SkiaSharp rendering module and user control (supporting OpenGL hardware acceleration)

## ScottPlot 4.0.29 (in development)
* `Plot.PlotFill()` can be used to make scatter plots with shaded regions. Giving it a single pair of X/Y values (`xs, ys`) lets you shade beneath the curve to the `baseline` value (which defaults to 0). You can also give it a pair of X/Y values (`xs1, ys1, xs2, ys2`) and the area between the two curves will be shaded (the two curves do not need to be the same length). See cookbook for examples. (#255) _Thanks @ckovamees_
* `DataGen.Range()` now has `includeStop` argument to include the last value in the returned array.
* `Tools.Pad()` has been created to return a copy of a given array padded with data values on each side. (#255) _Thanks @ckovamees_

Expand Down
56 changes: 56 additions & 0 deletions src/ScottPlot/Plot.cs
Expand Up @@ -430,6 +430,62 @@ public void Clear(Predicate<Plottable> plottablesToClear)
return scatterPlot;
}

public PlottablePolygon PlotFill(
double[] xs,
double[] ys,
string label = null,
double lineWidth = 0,
Color? lineColor = null,
bool fill = true,
Color? fillColor = null,
double fillAlpha = 1,
double baseline = 0
)
{
if (xs.Length != ys.Length)
throw new ArgumentException("xs and ys must all have the same length");

double[] xs2 = Tools.Pad(xs, cloneEdges: true);
double[] ys2 = Tools.Pad(ys, padWithLeft: baseline, padWithRight: baseline);

return PlotPolygon(xs2, ys2, label, lineWidth, lineColor, fill, fillColor, fillAlpha);
}

public PlottablePolygon PlotFill(
double[] xs1,
double[] ys1,
double[] xs2,
double[] ys2,
string label = null,
double lineWidth = 0,
Color? lineColor = null,
bool fill = true,
Color? fillColor = null,
double fillAlpha = 1,
double baseline = 0
)
{
if ((xs1.Length != ys1.Length) || (xs2.Length != ys2.Length))
throw new ArgumentException("xs and ys for each dataset must have the same length");

int pointCount = xs1.Length + xs2.Length;
double[] bothX = new double[pointCount];
double[] bothY = new double[pointCount];

// copy the first dataset as-is
Array.Copy(xs1, 0, bothX, 0, xs1.Length);
Array.Copy(ys1, 0, bothY, 0, ys1.Length);

// copy the second dataset in reverse order
for (int i = 0; i < xs2.Length; i++)
{
bothX[xs1.Length + i] = xs2[xs2.Length - 1 - i];
bothY[ys1.Length + i] = ys2[ys2.Length - 1 - i];
}

return PlotPolygon(bothX, bothY, label, lineWidth, lineColor, fill, fillColor, fillAlpha);
}

public PlottableFunction PlotFunction(
Func<double, double?> function,
Color? color = null,
Expand Down
88 changes: 88 additions & 0 deletions tests/PlotTypes/Fill.cs
@@ -0,0 +1,88 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace ScottPlotTests.PlotTypes
{
public class Fill
{
[Test]
public void Test_Fill_OneCurve()
{
double[] xs = ScottPlot.DataGen.Range(0, 10, .1, true);
double[] ys = ScottPlot.DataGen.Sin(xs);

var plt = new ScottPlot.Plot(400, 300);
plt.PlotFill(xs, ys, fillAlpha: .5);
plt.PlotHLine(0, color: Color.Black, lineStyle: ScottPlot.LineStyle.Dash);
plt.AxisAuto(0);

TestTools.SaveFig(plt);
}

[Test]
public void Test_Fill_OneCurveWithBaseline()
{
double[] xs = ScottPlot.DataGen.Range(0, 10, .1, true);
double[] ys = ScottPlot.DataGen.Sin(xs);

var plt = new ScottPlot.Plot(400, 300);
plt.PlotFill(xs, ys, fillAlpha: .5, baseline: .5);
plt.PlotHLine(0.5, color: Color.Black, lineStyle: ScottPlot.LineStyle.Dash);
plt.AxisAuto(0);

TestTools.SaveFig(plt);
}

[Test]
public void Test_Fill_Between()
{
// the sine wave will have ~100 points
double[] xs1 = ScottPlot.DataGen.Range(1, 10, .1, true);
double[] ys1 = ScottPlot.DataGen.Sin(xs1);

// the sine wave will have ~20 points
double[] xs2 = ScottPlot.DataGen.Range(1, 10, .5, true);
double[] ys2 = ScottPlot.DataGen.Cos(xs2);

var plt = new ScottPlot.Plot(400, 300);
plt.PlotFill(xs1, ys1, xs2, ys2, fillAlpha: .3);
plt.PlotScatter(xs1, ys1, Color.Black, 2);
plt.PlotScatter(xs2, ys2, Color.Black, 2);
plt.AxisAuto(0);

TestTools.SaveFig(plt);
}

[Test]
public void Test_Fill_SeveralCurvesWithLegend()
{
double[] blueLineXs = { 0, 3, 5.5, 8, 10, 14, 15 };
double[] blueLineYs = { 10, 13, 10, 13, 11, 15, 15 };

double[] greenLineXs = { 0, 5, 6.5, 8, 11, 15 };
double[] greenLineYs = { 6, 2, 3.5, 2, 5, 1 };

double[] redLineXs = { 0, 5, 8, 15 };
double[] redLineYs = { 2, 7, 4, 11 };

var plt = new ScottPlot.Plot(400, 300);

plt.PlotFill(blueLineXs, blueLineYs,
fillColor: Color.Blue, fillAlpha: .5, label: "blue");
plt.PlotFill(redLineXs, redLineYs,
fillColor: Color.Red, fillAlpha: .5, label: "red");
plt.PlotFill(greenLineXs, greenLineYs,
fillColor: Color.Green, fillAlpha: .5, label: "green");

plt.Legend(shadowDirection: ScottPlot.shadowDirection.none);

plt.AxisAuto(0, 0);

TestTools.SaveFig(plt);
}
}
}

0 comments on commit 042e582

Please sign in to comment.