Peak Shading Under a Curve

TeeChart for Microsoft Visual Studio .NET, Xamarin Studio (Android, iOS & Forms) & Monodevelop.
Post Reply
pmartin
Newbie
Newbie
Posts: 1
Joined: Mon Jul 29, 2019 12:00 am

Peak Shading Under a Curve

Post by pmartin » Thu Aug 08, 2019 2:56 pm

I am trying to implement shading under a curve. I have successfully used the information/examples from this post: https://www.steema.com/support/viewtopic.php?t=10307

That post explains how to use the SeriesRegionTool to color an area under a curve starting at one X value (X1), ending at a second X value (X2) with an origin value (Y) that defines the baseline of the shaded area by the points (X1, Y) and (X2, Y). The SeriesRegionTool works great for this!

I will now need to extend this solution to handle shading when the baseline points do not share the same Y value (i.e. the baseline is sloped). Here's an example image where the left curve is the straight baseline I currently have working with the SeriesRegionTool and the right curve shows the sloped baseline that I need help implementing:
example-sloped-baseline.PNG
example-sloped-baseline.PNG (6.41 KiB) Viewed 8895 times
I'd like to continue using the SeriesRegionTool for this - but it doesn't seem to be able to handle multiple origin points. Can you please help me understand the best way to shade the area under the curve with a sloped baseline?

Thanks!!

Christopher
Guru
Posts: 1603
Joined: Fri Nov 15, 2002 12:00 am

Re: Peak Shading Under a Curve

Post by Christopher » Fri Aug 09, 2019 8:13 am

pmartin wrote:
Thu Aug 08, 2019 2:56 pm
Can you please help me understand the best way to shade the area under the curve with a sloped baseline?
I think the only way you're going to be able to do this is to derive a new Tool from our SeriesRegionTool and then adapt it to your needs. Unfortunately some of the key methods of the SeriesRegionTool are private rather than protected virtual, but in order to help you we can share some of our source code with you:

Code: Select all

    public class MySeriesRegionTool : SeriesRegionTool
    {
        public MySeriesRegionTool(Chart c) : base(c) { }

        protected override void ChartEvent(EventArgs e)
        {
            if (Series != null)
            {
                if (((e is BeforeDrawSeriesEventArgs) && (DrawBehindSeries))
                    || (e is AfterDrawSeriesEventsArgs) && (!DrawBehindSeries))
                {
                    DrawRegion();
                }
            }
        }

        /// <summary>
        /// Using point x coordinate it calculates point y coordinate
        /// </summary>
        /// <param name="val">intersection point x coordinate</param>
        /// <param name="y">returns intersection point y coordinate.</param>
        /// <returns>Point index</returns>
        private int IntersectionPoint(double val, out double y)
        {
            var i = 0;
            y = Series.mandatory[i];
            while ((val > Series.notMandatory[i]) && (i < Series.Count))
            {
                i++;
            }

            // We have two choices:
            // #1: value is exactly at point coordinate
            // #2: value is between two points - use linear interpolation to calculate y

            if (val == Series.notMandatory[i])
            {
                y = Series.mandatory[i];
            }
            else
            {
                double k;
                if ((i > 0) && (i < Series.Count))
                {
                    k = Series.mandatory[i] - Series.mandatory[i - 1];
                    k /= (Series.notMandatory[i] - Series.notMandatory[i - 1]);
                    y = Series.mandatory[i - 1] + k * (val - Series.notMandatory[i - 1]);
                }
            }

            return i;
        }

        private void DrawRegion()
        {
            if (Active && (Chart != null) && (Series != null))
            {
                var lb = Series.notMandatory.Minimum;
                var ub = Series.notMandatory.Maximum;
                if (!AutoBound)
                {
                    lb = Math.Max(lb, LowerBound);
                    ub = Math.Min(ub, UpperBound);
                }

                // plot only if it makes sense
                if ((ub > Series.notMandatory.Minimum) && (lb < Series.notMandatory.Maximum))
                {
                    var first = IntersectionPoint(lb, out var yl);
                    var last = IntersectionPoint(ub, out var yu);
                    if (last < first)
                    {
                        Utils.SwapInteger(ref first, ref last);
                    }

                    var plen = last - first + 1;
                    var pts = new Point[plen + 4]; // need four extra points

                    for (var i = 0; i < plen; i++)
                    {
                        pts[i].X = Series.CalcXPos(i + first);
                        pts[i].Y = Series.CalcYPos(i + first);
                    }

                    // upper bound intersect point
                    pts[plen].X = Series.CalcXPosValue(ub);
                    pts[plen].Y = Series.CalcYPosValue(yu);

                    // upper bound origin point
                    pts[plen + 1].X = pts[plen].X;
                    pts[plen + 1].Y = UseOrigin ? Series.CalcYPosValue(Origin) : Series.GetVertAxis.IEndPos;

                    // lower bound origin point
                    pts[plen + 2].X = Series.CalcXPosValue(lb);
                    pts[plen + 2].Y = pts[plen + 1].Y;

                    // lower bound intersect point
                    pts[plen + 3].X = pts[plen + 2].X;
                    pts[plen + 3].Y = Series.CalcYPosValue(yl);

                    var gr = Chart.Graphics3D;
                    gr.Brush = Brush;
                    gr.Pen = Pen;

                    var zpos = DrawBehindSeries ? Series.EndZ : Series.StartZ;

                    var tmpR = gr.RectFromRectZ(Chart.ChartRect, zpos);
                    gr.ClipRectangle(tmpR);

                    gr.Polygon(zpos, pts);
                    gr.UnClip();
                }
            }
        }
    }
You can see this working by using this class in the example you mentioned, i.e.

Code: Select all

        private void InitializeChart()
        {
            tChart1.Aspect.View3D = false;

            var nSeries = 4;

            var yPos = new int[nSeries];
            var fast = new FastLine[nSeries];
            var region = new MySeriesRegionTool[nSeries];

            for (var i = 0; i < nSeries; i++)
            {
                yPos[i] = i * 5;
                fast[i] = new FastLine(tChart1.Chart);
                if (i % 2 == 0)
                {
                    for (var j = 0; j < 200; j++) fast[i].Add(Math.Sin((double)j / 10) + yPos[i]);
                }
                else
                {
                    for (var j = 0; j < 200; j++) fast[i].Add(Math.Cos((double)j / 10) + yPos[i]);
                }

                region[i] = new MySeriesRegionTool(tChart1.Chart);
                region[i].Series = fast[i];
                region[i].Origin = yPos[i];
                region[i].UseOrigin = true;
                region[i].Brush.Style = System.Drawing.Drawing2D.HatchStyle.Vertical;
                region[i].Brush.Transparency = 100;
                region[i].Pen.Visible = false;
                region[i].Brush.ForegroundColor = fast[i].Color;
            }
        }
Please be aware that deriving TeeChart classes is perfectly valid under our license, although we do count it as 'source-code modification' and as such give no support to such classes. It is worth mentioning that there are a couple of options if you are not able to make the changes yourself to your satisfaction:
1) request a new feature request on http://bugs.teechart.net/
2) ask sales@steema.com for a quote from us for the work
Best Regards,
Christopher Ireland / Development & Support
Steema Software
Avinguda Montilivi 33, 17003 Girona, Catalonia
Tel: 34 972 218 797
http://www.steema.com
Instructions - How to post in this forum

Post Reply