System.NullReferenceException while trying to pan before the chart is plotted

Oystein Bjorke 10 years ago 0
This discussion was imported from CodePlex

jmprog wrote at 2014-01-23 17:49:

Hello,

I don't know if this bug has already been reported.

I'm currently using OxyPlot with .net 3.5 with Windows Forms and I'm using the heatmapseries with contour series. (The bug happens with the heatmapseries)

When I plot a grid of 400x400, it takes a few seconds to plot and the user has time to interact with the application.

So I reveive an Unhandled Exception everytime I try to Pan the plot before the chart was actually plotted :

System.NullReferenceException: Object reference not set to an instance of an object.
at OxyPlot.Series.XYAxisSeries.InverseTransform(ScreenPoint p) in c:\Users\jmbegin\Desktop\Oxyplot\oxyplot_36df819096f6\Source\OxyPlot\Series\XYAxisSeries.cs:line 126
at OxyPlot.Series.HeatMapSeries.GetNearestPoint(ScreenPoint point, Boolean interpolate) in c:\Users\jmbegin\Desktop\Oxyplot\oxyplot_36df819096f6\Source\OxyPlot\Series\HeatMapSeries.cs:line 252
at OxyPlot.Series.Series.HitTest(ScreenPoint point, Double tolerance) in c:\Users\jmbegin\Desktop\Oxyplot\oxyplot_36df819096f6\Source\OxyPlot\Series\Series.cs:line 123
at OxyPlot.PlotModel.HandleMouseDown(Object sender, OxyMouseEventArgs e) in c:\Users\jmbegin\Desktop\Oxyplot\oxyplot_36df819096f6\Source\OxyPlot\PlotModel\PlotModel.MouseEvents.cs:line 82
at OxyPlot.WindowsForms.Plot.OnMouseDown(MouseEventArgs e) in c:\Users\jmbegin\Desktop\Oxyplot\oxyplot_36df819096f6\Source\OxyPlot.WindowsForms\Plot.cs:line 482
at System.Windows.Forms.Control.WmMouseDown(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Using a debugger, I tracked the source of the bug. As the exception info said, it happens in HeatMapSeries.GetNearestPoint when it calls this.InverseTransform
    public DataPoint InverseTransform(ScreenPoint p)
    {
           return this.XAxis.InverseTransform(p.X, p.Y, this.YAxis);
    }
Where my XAxis and YAxis are null, which explains the NullReferenceException

jmprog wrote at 2014-01-23 18:03:

I solved this problem for my project by adding
        if (this.XAxis == null || this.YAxis == null) return null;
In the HeatMapSeries.GetNearestPoint so we don't encounter the null reference exception.

This does not have the exact behavior I wanted as it seem to execute all the pans done while not plotted once it plotted, so it's a weird animation.
But for my project, it is enough as it's a rare condition and it still gives the result I want.

objo wrote at 2014-01-23 18:38:

Are you updating/rendering on multiple threads?
I could not reproduce this bug in the example browser (using WinForms and .NET 4.0).

We can add the test to the HeatMapSeries.GetNearestPoint, but I suspect this is not the only place.
Maybe it would be better to handle this in the PlotModel.HandleMouseDown method - if the plot model is not updated it should not try to perform the hit testing. It is probably more code than the single null reference test, but could solve similar problems in other series.

jmprog wrote at 2014-01-23 19:16:

Humm yes, I added added recently code to generate my PlotModel in a different thread to try and reduce the time the application is hanging.
Although, I'm adding the PlotModel to the Plot.Model only after the thread joined the main one.

I tried removing all the threading I added and the bug still happens.
I also got the same issue with ContourSeries.GetNearestPoint where I added the same fix "if (this.XAxis == null || this.YAxis == null) return null;"

I also tried to reproduce with the example browser and I didn't reproduce it, I'm still investigating on my side to see what is wrong,

jmprog wrote at 2014-01-23 21:59:

Hello objo,
after a lot of searching and testing, it really seems like a timing problem where the mousedown event gets triggered before the plotModel is fully updated.
I've had tests that if I stopped the application the problem would not happen like if the delay I gave was enough to let the update get done before the mousedown event.

I've also suceeded in reproducing the bug with the createpeaks exemple in .net 4.5 by adding calculating delay before the update.

To reproduce you can add the loop before adding the model. I saw it happened like that if you can pan before it's plotted.
        private void InitPlot()
        {
            int count = 0;
            for (int i = 0; i < 10000; i++)
            {
                count += i;
                for (int j = 0; j < 10000; j++)
                {
                    if (count > 0)
                        count -= j;
                    else
                        count += j;
                }
            }
            plot1.Model = vm.SelectedExample != null ? vm.SelectedExample.PlotModel : null;
            plot1.BackColor = vm.PlotBackground;
        }
Else what I did first was to modify the WindowsFormsDemo by adding a button to refresh and the calculating Delay :
namespace WindowsFormsDemo
{
    using System;
    using System.Drawing;
    using System.Windows.Forms;

    using OxyPlot;
    using OxyPlot.Axes;
    using OxyPlot.Series;

    public partial class Form1 : Form
    {
        Button refreshButton;
        public Form1()
        {
            InitializeComponent();

            refreshButton = new Button();
            refreshButton.Location = new Point(this.Width / 2, 30);
            refreshButton.Click += refreshButton_Click;
            this.Controls.Add(refreshButton);
            plot1.SendToBack();

            plot1.Model = CreatePeaks();
        }

        public void refreshButton_Click(object sender, EventArgs e)
        {
            int count = 0;
            for (int i = 0; i < 10000; i++)
            { 
                count += i;
                for (int j = 0; j < 10000; j++)
                {
                    if (count > 0)
                        count -= j;
                    else
                        count += j;
                }
            }
            refreshButton.Text = count.ToString();
            plot1.Model = CreatePeaks();
        }

        public static PlotModel CreatePeaks(OxyPalette palette = null, bool includeContours = true)
        {
            double x0 = -3.1;
            double x1 = 3.1;
            double y0 = -3;
            double y1 = 3;
            Func<double, double, double> peaks = (x, y) => 3 * (1 - x) * (1 - x) * Math.Exp(-(x * x) - (y + 1) * (y + 1)) - 10 * (x / 5 - x * x * x - y * y * y * y * y) * Math.Exp(-x * x - y * y) - 1.0 / 3 * Math.Exp(-(x + 1) * (x + 1) - y * y);
            var xvalues = ArrayHelper.CreateVector(x0, x1, 400);
            var yvalues = ArrayHelper.CreateVector(y0, y1, 400);
            var peaksData = ArrayHelper.Evaluate(peaks, xvalues, yvalues);

            var model = new PlotModel("Peaks");
            model.Axes.Add(new LinearColorAxis { Position = AxisPosition.Right, Palette = palette ?? OxyPalettes.Jet(500), HighColor = OxyColors.Gray, LowColor = OxyColors.Black });

            var hms = new HeatMapSeries { X0 = x0, X1 = x1, Y0 = y0, Y1 = y1, Data = peaksData };
            model.Series.Add(hms);
            if (includeContours)
            {
                var cs = new ContourSeries
                {
                    Color = OxyColors.Black,
                    FontSize = 0,
                    ContourLevelStep = 1,
                    LabelBackground = OxyColors.Undefined,
                    ColumnCoordinates = yvalues,
                    RowCoordinates = xvalues,
                    Data = peaksData
                };
                model.Series.Add(cs);
            }

            return model;
        }
    }
}

elwut wrote at 2014-02-21 15:41:

I think i have an issue with something related to this.
<Exception><ExceptionType>System.NullReferenceException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>Referencia a objeto no establecida como instancia de un objeto.</Message><StackTrace>   en OxyPlot.ScreenPointHelper.FindNearestPointOnPolyline(ScreenPoint point, IList`1 points) en c:\TeamCity\buildAgent\work\3b9fcf1ba397d0ed\Source\OxyPlot\Rendering\ScreenPointHelper.cs:línea 56
   en OxyPlot.Annotations.PathAnnotation.HitTest(ScreenPoint point, Double tolerance) en c:\TeamCity\buildAgent\work\3b9fcf1ba397d0ed\Source\OxyPlot\Annotations\PathAnnotation.cs:línea 424
   en OxyPlot.PlotModel.HandleMouseDown(Object sender, OxyMouseEventArgs e) en c:\TeamCity\buildAgent\work\3b9fcf1ba397d0ed\Source\OxyPlot\PlotModel\PlotModel.MouseEvents.cs:línea 86
   en OxyPlot.WindowsForms.Plot.OnMouseDown(MouseEventArgs e) en c:\TeamCity\buildAgent\work\3b9fcf1ba397d0ed\Source\OxyPlot.WindowsForms\Plot.cs:línea 397
   en System.Windows.Forms.Control.WmMouseDown(Message&amp;amp; m, MouseButtons button, Int32 clicks)
   en System.Windows.Forms.Control.WndProc(Message&amp;amp; m)
   en System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   en System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG&amp;amp; msg)
   en System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
   en System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   en System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   en QCEP.Program.Main() en c:\Users\gbaquedano\Documents\Visual Studio 2013\Projects\QCEP\QCEP\Program.cs:línea 20
   en System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   en System.Runtime.Hosting.ApplicationActivator.CreateInstance(ActivationContext activationContext, String[] activationCustomData)
   en Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssemblyDebugInZone()
   en System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   en System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   en System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   en System.Threading.ThreadHelper.ThreadStart()</StackTrace><ExceptionString>System.NullReferenceException: Referencia a objeto no establecida como instancia de un objeto.
   en OxyPlot.ScreenPointHelper.FindNearestPointOnPolyline(ScreenPoint point, IList`1 points) en c:\TeamCity\buildAgent\work\3b9fcf1ba397d0ed\Source\OxyPlot\Rendering\ScreenPointHelper.cs:línea 56
   en OxyPlot.Annotations.PathAnnotation.HitTest(ScreenPoint point, Double tolerance) en c:\TeamCity\buildAgent\work\3b9fcf1ba397d0ed\Source\OxyPlot\Annotations\PathAnnotation.cs:línea 424
   en OxyPlot.PlotModel.HandleMouseDown(Object sender, OxyMouseEventArgs e) en c:\TeamCity\buildAgent\work\3b9fcf1ba397d0ed\Source\OxyPlot\PlotModel\PlotModel.MouseEvents.cs:línea 86
   en OxyPlot.WindowsForms.Plot.OnMouseDown(MouseEventArgs e) en c:\TeamCity\buildAgent\work\3b9fcf1ba397d0ed\Source\OxyPlot.WindowsForms\Plot.cs:línea 397
   en System.Windows.Forms.Control.WmMouseDown(Message&amp;amp; m, MouseButtons button, Int32 clicks)
   en System.Windows.Forms.Control.WndProc(Message&amp;amp; m)
   en System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   en System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG&amp;amp; msg)
   en System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
   en System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   en System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
Issue happens when you click the plot when it is drawing and hasn't finished. The click event gets triggered even the model is not fully updated.
If you wait for it to finish (like 100ms in my current test) you dont get the crash.

jmprog wrote at 2014-02-21 16:41:

It does sound exactly like what I had.
I also specified how to solve this quickly and locally by adding null reference checks directly in the library in the getNearestPoint functions or in your case FindNearestPointOnPolyline.

The ideal solution would be as objo said :
Maybe it would be better to handle this in the PlotModel.HandleMouseDown method - if the plot model is not updated it should not try to perform the hit testing.
You can see for yourself which one you want to implement.

I don't have time to look into this myself, but if I'm not the only one that had this bug it would be a good idea to create an issue about it.

objo wrote at 2014-02-25 11:19:

jmprog: thanks for providing the code to reproduce the error. I have added the issue: https://oxyplot.codeplex.com/workitem/10138