This project is read-only.
1
Vote

Overflow exception when drawing a straight line

description

In Sparrow.Chart.WPF.Series.SeriesBase, if the line to be drawn is a straight line, the range calculation results in a division by zero, which manifests as an application crash:
Overflow error
at System.Drawing.Graphics.CheckErrorStatus(Int32 status) 
at System.Drawing.Graphics.DrawLine(Pen pen, Single x1, Single y1, Single x2, Single y2) 
at System.Drawing.Graphics.DrawLine(Pen pen, PointF pt1, PointF pt2) 
at Sparrow.Chart.AreaContainer.DrawPath(SeriesBase series, Pen pen) 
at Sparrow.Chart.SeriesContainer.Draw() 
at Sparrow.Chart.SeriesContainer.b__2() 
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) 
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
This exception would occur in the Task Manager demo if the CPU level stayed constant long enough for the graph to eventually contain all the same number.

The following fix in "https://sparrowtoolkit.codeplex.com/SourceControl/latest#Work/Source/Sparrow.Chart/WPF/Series/SeriesBase.cs" appears to correct the issue for me.

Change the following code in "NormalizePoint" (in a straight line, YMax and YMin will be the same number, resulting in division by zero):
result.X = ((pt.X - XMin) * (SeriesContainer.Collection.ActualWidth / (XMax - XMin)));
result.Y = (SeriesContainer.Collection.ActualHeight - ((pt.Y - YMin) * SeriesContainer.Collection.ActualHeight) / (YMax - YMin));
To the following code fix:
result.X = ((XMax - XMin) == 0) ? pt.X : ((pt.X - XMin) * (SeriesContainer.Collection.ActualWidth / (XMax - XMin)));
result.Y = ((YMax - YMin) == 0) ? pt.Y : (SeriesContainer.Collection.ActualHeight - ((pt.Y - YMin) * SeriesContainer.Collection.ActualHeight) / (YMax - YMin));

comments

jcw wrote Apr 2, 2014 at 8:42 AM

Update: For now, I have forked this project so I can use a patched version of Sparrow from my build agent's nuget feed, see this changeset for the fix I'm using: https://github.com/jcw-/sparrowtoolkit/commit/2a19dd24a39c76b869b0fff148b065af118066e7

Foske76 wrote Apr 4, 2014 at 8:56 AM

This fix appears incorrect to me. Of course it fixes the crash, but it completely ignores the fact that NormalizePoint actually scales and translates the region XMax-XMin to the region 0-ActualWidth and similar in the Y direction. Returning pt.X,pt.Y is therefore wrong (Easy to see if the input coordinates can be huge or negative).

Now, the correct fix is to return a value within the desired output range. Question is which value is correct. I'd suggest ActualWidth/2 instead of pt.X or ActualHeight/2 instead of pt.Y, which should make the line appear in the middle of the graph. Could you test this ?

Foske76 wrote Apr 4, 2014 at 9:24 AM

Suggested fix:
            result.X = ((XMax - XMin) == 0) ? (SeriesContainer.Collection.ActualWidth / 2) : ((pt.X - XMin) * (SeriesContainer.Collection.ActualWidth / (XMax - XMin)));
            result.Y = ((YMax - YMin) == 0) ? (SeriesContainer.Collection.ActualHeight / 2) : (SeriesContainer.Collection.ActualHeight - ((pt.Y - YMin) * SeriesContainer.Collection.ActualHeight) / (YMax - YMin));

jcw wrote Apr 4, 2014 at 9:48 AM

Good point - in my case the straight line that would typically occur was at zero. I'm trying out your suggested fix right now...

jcw wrote Apr 4, 2014 at 10:51 AM

I'm not sure defaulting to the middle for a straight line is the best, it kind of looks weird, at least in my application, when there's no activity for a few minutes (long enough to populate the viewable width with all zeros) and then the series suddenly jumps up to the middle (now looking like a non-zero representation).

I think a good compromise for the "constant line value" flow would be to either A or B, but C would be fine worst case, as at least it doesn't cause an exception:

A) Outside "actual" SeriesContainer dimensions = draw at closest boundary, Inside = use pt.X or pt.Y (this is really just truncating the value to the drawing boundaries)
B) Always draw flat lines at closest boundary (e.g. top if above mid-point, bottom if not)
C) Always draw flat lines in the center

I tried out option A and it works well enough, although it seems to render a constant line of zero's at the top of the drawing space instead of the bottom (not sure why, the values themselves are correct), but that's a separate concern (which doesn't personally bother me).

Code for option A:
public Point NormalizePoint(Point pt)
{
    Point result = new Point();
    //if (this.XAxis != null)
    //    result.X = this.XAxis.DataToPoint(pt.X);
    //if (this.YAxis != null)
    //    result.Y = this.YAxis.DataToPoint(pt.Y);

    if ((XMax - XMin) == 0)
    {
        result.X = Math.Min(Math.Max(pt.X, 0), SeriesContainer.Collection.ActualWidth);
    }
    else
    {
        result.X = ((pt.X - XMin) * (SeriesContainer.Collection.ActualWidth / (XMax - XMin)));
    }

    if ((YMax - YMin) == 0)
    {
        result.Y = Math.Min(Math.Max(pt.Y, 0), SeriesContainer.Collection.ActualHeight);
    }
    else
    {
        result.Y = (SeriesContainer.Collection.ActualHeight - ((pt.Y - YMin) * SeriesContainer.Collection.ActualHeight) / (YMax - YMin));
    }

    return result;
}

Foske76 wrote Apr 7, 2014 at 9:28 AM

Thanks for your comments.
I was wondering whether your graph would benefit from fixed axes or a minimum range.

To some extend I like your idea, but I still have my doubts about mapping values to pixels on a 1-1 basis. This would imply that a flat line in the middle of the graph suddenly clips to the top/right when you resize the graph area.

Then again, there is no way you can do this right for all situations. Maybe we should go with your proposed solution and if you don't like the behavior, you can always fix your axes.

jcw wrote Apr 7, 2014 at 3:47 PM

Yeah, there's not really an obvious choice here for me either, other than at a minimum, preventing the overflow exception.

True, I could try fixed axes or a minimum range, perhaps that would even prevent the overflow exception. I'll try it out.