Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PlotPolygon, PlotFill, and PlotFillAboveBelow #255

Closed
2 tasks
ckovamees opened this issue Feb 14, 2020 · 12 comments
Closed
2 tasks

PlotPolygon, PlotFill, and PlotFillAboveBelow #255

ckovamees opened this issue Feb 14, 2020 · 12 comments

Comments

@ckovamees
Copy link

ckovamees commented Feb 14, 2020

Hi Scott,
Amazing activity in this project. If you ever lack things to do here is a feature request.... :-)

For my hobby project I would find it useful to render an area between curves in a light shade of green or red/or any other color for that matter.
image

Edited by Scott to add todo list:

    • add arguments to PlotSignal() to support shading under the curve
    • add arguments to PlotSignalConst() to support shading under the curve
    • add arguments to PlotScatter() to support shading under the curve
  • create PlotPolygon() to allow rendering of arbitrary shapes
  • create PlotRectangle() to allow easy outlining of data areas
@swharden
Copy link
Member

swharden commented Feb 14, 2020

This is an excellent idea @ckovamees. I'll look into it shortly!

@swharden
Copy link
Member

swharden commented Apr 5, 2020

It took a while, but there's some movement on this. I just finished creating a Polygon object, and this code can be extended to make it easier to plot shaded areas between curves. I'll keep working on this.

image

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

plt.PlotPolygon(
    xs: new double[] { 2, 8, 6, 4 },
    ys: new double[] { 3, 4, 0.5, 1 },
    label: "polygon A", lineWidth: 2, fillAlpha: .8,
    lineColor: System.Drawing.Color.Black);

plt.PlotPolygon(
    xs: new double[] { 3, 2.5, 5 },
    ys: new double[] { 4.5, 1.5, 2.5 },
    label: "polygon B", lineWidth: 2, fillAlpha: .8,
    lineColor: System.Drawing.Color.Black);

plt.Title($"Polygon Demonstration");
plt.Legend();

@swharden
Copy link
Member

swharden commented Apr 5, 2020

@ckovamees this might get a little more refined (watch the cookbook over the next few days), but I think this is a pretty good result! I'll close this issue since the core problem is solved.

image

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

// generate sample data
Random rand = new Random(0);
var dataY = DataGen.RandomWalk(rand, 1000, offset: -10);
var dataX = DataGen.Consecutive(dataY.Length, spacing: 0.025);

// create an array with an extra point on each side of the data
double baseline = 0;
var xs = new double[dataX.Length + 2];
var ys = new double[dataY.Length + 2];
Array.Copy(dataX, 0, xs, 1, dataX.Length);
Array.Copy(dataY, 0, ys, 1, dataY.Length);
xs[0] = dataX[0];
xs[xs.Length - 1] = dataX[dataX.Length - 1];
ys[0] = baseline;
ys[ys.Length - 1] = baseline;

// separate the data into two arrays (for positive and negative)
double[] neg = new double[ys.Length];
double[] pos = new double[ys.Length];
for (int i = 0; i < ys.Length; i++)
{
    if (ys[i] < 0)
        neg[i] = ys[i];
    else
        pos[i] = ys[i];
}

// now plot the arrays as polygons
plt.PlotPolygon(xs, neg, "negative", lineWidth: 1,
    lineColor: Color.Black, fillColor: Color.Red, fillAlpha: .5);
plt.PlotPolygon(xs, pos, "positive", lineWidth: 1,
    lineColor: Color.Black, fillColor: Color.Green, fillAlpha: .5);
plt.Title("Shaded Line Plot (negative vs. positive)");
plt.Legend(location: ScottPlot.legendLocation.lowerLeft);
plt.AxisAuto(0);

@swharden swharden closed this as completed Apr 5, 2020
swharden added a commit that referenced this issue Apr 5, 2020
swharden added a commit that referenced this issue Apr 5, 2020
@ckovamees
Copy link
Author

Thanks Scott, worked great for me.

I think you added a DataSet class that screwed me over a bit ;-) but I renamed my class. I also had to create a test project to understand howto create the area between two curves. I ended up creating a list, adding the first curve and then reversing the second curves' array to make a continuous 'polygon'.

Again - thanks for implementing this - stay healthy and safe!

@swharden
Copy link
Member

swharden commented Apr 7, 2020

... how to create the area between two curves. I ended up creating a list, adding the first curve and then reversing the second curves' array to make a continuous 'polygon'

I forgot about this! Do you think that code would be useful to share here? I should add a similar example to the cookbook.

Maybe I should make a little tool to return 1 polygon given two curves 🤔

@ckovamees
Copy link
Author

ckovamees commented Apr 7, 2020

I think a utility method would make it easier for folks. It's trivial once you know how to do it... There could potential be problems if x-values aren't common. (?)
This is what I did to try it.

static double[] datesOrig = { 43839, 43840, 43843, 43844, 43845, 43846, 43847, 43851, 43852, 43853, 43854, 43857, 43858, 43859, 43860, 43861, 43864, 43865, 43866, 43867, 43868, 43869, 43870, 43871, 43872, 43873, 43874, 43875, 43876, 43877, 43878, 43879, 43880, 43881 };
        static double[] curveA = { 226.5475, 222.54250000000002, 222.155, 221.57500000000002, 221.57500000000002, 221.57500000000002, 221.57500000000002, 220.96500000000003, 220.43, 219.60250000000002, 219.60250000000002, 219.995, 220.03000000000003, 220.325, 220.525, 220.6325, 220.64749999999998, 219.1, 217.97750000000002, 217.95999999999998, 218.125, 218.125, 218.125, 218.115, 218.03250000000003, 218.2375, 219.52750000000003, 219.52750000000003, 219.52750000000003, 219.52750000000003, 219.52750000000003, 221.1825, 223.1775, 224.1625 };
        static double[] curveB = { 228.095, 226.12, 226.12, 225.54000000000002, 225.54000000000002, 225.54000000000002, 225.54000000000002, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96, 224.96,
 };
Plot plt = new Plot(600, 400);

List<double> dates = datesOrig.ToList();
dates.AddRange(datesOrig.Reverse());

List<double> data = curveB.ToList();
data.AddRange(curveA.Reverse().ToList());

plt.PlotPolygon(dates.ToArray(), data.ToArray(), "Area", fillColor: Color.Red, fillAlpha: .5);

PolyGraphTest

@swharden
Copy link
Member

swharden commented Apr 8, 2020

@ckovamees I created Plot.PlotFill() method which handles the confusing array manipulation behind the scenes. Now you can just give it a curve and it will shade between the curve and the baseline (which the user can set). You can also give it two curves (they don't have to be the same number of points) and it will shade between them. PlotFill() just creates the arrays to shade the right region, then calls PlotPolygon().

What do you think?

double[] xs = DataGen.Range(0, 10, .1, true);
double[] sin = DataGen.Sin(xs);
double[] cos = DataGen.Cos(xs);

PlotFill() between a curve and baseline

plt.PlotFill(xs, sin, "sin", lineWidth: 2, fillAlpha: .5);
plt.PlotFill(xs, cos, "cos", lineWidth: 2, fillAlpha: .5);
plt.PlotHLine(0, color: Color.Black);
plt.AxisAuto(0);
plt.Legend(location: legendLocation.lowerLeft);

PlotTypes_Fill_FillBeneathCurve

PlotFill() between two curves

plt.PlotFill(xs, sin, xs, cos, fillAlpha: .5);
plt.PlotScatter(xs, sin, Color.Black);
plt.PlotScatter(xs, cos, Color.Black);
plt.AxisAuto(0);

PlotTypes_Fill_FillBetweenCurves

@swharden
Copy link
Member

swharden commented Apr 8, 2020

This solution still doesn't actually support the "over/under" display style you initially posted about. I wonder if that can be added easily somehow 🤔

@swharden swharden reopened this Apr 8, 2020
@ckovamees
Copy link
Author

ckovamees commented Apr 8, 2020

That's awesome!

Perhaps optional options to PlotFill() for when the first set (really y-value) xs/sin is greater than xs/cos. positiveFillColor: negativeFillColor: or greaterFillColor:/lesserFillColor: - not ideal.

I never like to have hardocded number of options. Just two sets of arguments in this case, I usually prefer lists or arrays of options. Not sure how that would work though...

--- Just thought about this as a conversation starter --> how about an optional callback function that can be implemented by the caller to return properties for plotted values? It might be a little slow :-o if called for every point. (?)

@swharden
Copy link
Member

swharden commented Apr 10, 2020

@ckovamees both those options may work, but you're right they're not ideal because they add unnecessary complexity to the PlotFill() method that doesn't seem to belong there if users aren't going to use this advanced feature.

What about a separate PlotFillTwoColor() method? Maybe it could use a better name. Under the hood it just splits the user's array into two separate arrays (for above/below the baseline) then calls PlotFill() twice. You could use it like:

plt.PlotFillAboveBelow(xs, ys, Color.Green, Color.Red)

swharden added a commit that referenced this issue Apr 11, 2020
swharden added a commit that referenced this issue Apr 11, 2020
swharden added a commit that referenced this issue Apr 11, 2020
@swharden
Copy link
Member

I went ahead and did this - it looks good!

Thanks again for your initial suggestion @ckovamees, it led to a lot of great improvements for ScottPlot 👍

Random rand = new Random(0);
var ys = ScottPlot.DataGen.RandomWalk(rand, 1000, offset: -10);
var xs = ScottPlot.DataGen.Consecutive(ys.Length, spacing: 0.025);
var plt = new ScottPlot.Plot(400, 300);
plt.PlotFillAboveBelow(xs, ys, fillAlpha: .5, labelAbove: "above", labelBelow: "below");
plt.Legend(location: ScottPlot.legendLocation.lowerLeft);
plt.AxisAuto(0);

image

@swharden swharden changed the title Shade area in-between curves PlotPolygon, PlotFill, and PlotFillAboveBelow Apr 11, 2020
@ckovamees
Copy link
Author

Scott, the turnaround is amazing. This package/tool is great and your support is really phenomenal.

I don't think I expressed myself very well.

I was really looking for your PlotFill() function that would be independent of the baseline. In your Sin/Cos example above it would be fillColorAbove when Sin is larger than Cos and fillColorBelow when Cos is greater than Sin.

What you have provided so far helps me tremendously - thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants