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
Comments
This is an excellent idea @ckovamees. I'll look into it shortly! |
It took a while, but there's some movement on this. I just finished creating a 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(); |
@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. 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); |
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! |
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 🤔 |
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. (?) 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); |
@ckovamees I created 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 baselineplt.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); PlotFill() between two curvesplt.PlotFill(xs, sin, xs, cos, fillAlpha: .5);
plt.PlotScatter(xs, sin, Color.Black);
plt.PlotScatter(xs, cos, Color.Black);
plt.AxisAuto(0); |
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 🤔 |
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. (?) |
@ckovamees both those options may work, but you're right they're not ideal because they add unnecessary complexity to the What about a separate plt.PlotFillAboveBelow(xs, ys, Color.Green, Color.Red) |
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); |
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! |
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.
Edited by Scott to add todo list:
add arguments to PlotSignal() to support shading under the curveadd arguments to PlotSignalConst() to support shading under the curveadd arguments to PlotScatter() to support shading under the curveThe text was updated successfully, but these errors were encountered: