locked
Draw Performance with ZedGraph.ZedGraphControl with multiple graphs RRS feed

  • Question

  • I'm looking for a way to try to improve the gui response while the graph is being drawn or whatever. I'll give a little background about what my program is doing, basically we have a scanner device that scans and returns a spectrum. I want to graph the spectrum which isn't all that bad. The application recieves the data, creates the spectrum then calls ZedGraphControl.Refresh which makes it pause for a moment. It isn't bad but it gets completely out of hand when running multiple devices that are sweeping, completely crippling the Gui response.

    I started looking at this thread http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/b0e6e8c1-01eb-49fc-962b-93ce6ea1d791, I believe there is a way to improve i don't really know where to start. All I know is I can do all the work I need in a background thread until I call refresh.


    Wednesday, February 29, 2012 5:03 PM

Answers

  • We just ran into a similar problem updating a third party image box.

    So the problem here is that when you are trying to update all these ZedGraphControls, they are all pumping crazy amounts of messages to Main Thread (The Application.Run? It actually is working really hard!) When you saturate the messaging thread, you're entire GUI will start to freeze up.

    If you can set the data for all the ZedGraphs before they invalidate/refresh, you can simply call this.Invalidate on the entire form to get it to redraw. But even this has limits: all your rendering is still going on in the main thread, so you'll probably run into the same issue.

    Another option is to give each graph its own form running its own message thread by calling

    Application.Run(secondaryForm)

    in a background worker. Each of your graphs would appear in their own form, which can be undesirable. You'll also probably have to use .Invoke and .RequiresInvoke ALOT between the different forms. But it would solve your problem.

    Hope this helps!

    • Marked as answer by Kevin Chaves Monday, March 5, 2012 6:34 PM
    Wednesday, February 29, 2012 9:34 PM

All replies

  • We just ran into a similar problem updating a third party image box.

    So the problem here is that when you are trying to update all these ZedGraphControls, they are all pumping crazy amounts of messages to Main Thread (The Application.Run? It actually is working really hard!) When you saturate the messaging thread, you're entire GUI will start to freeze up.

    If you can set the data for all the ZedGraphs before they invalidate/refresh, you can simply call this.Invalidate on the entire form to get it to redraw. But even this has limits: all your rendering is still going on in the main thread, so you'll probably run into the same issue.

    Another option is to give each graph its own form running its own message thread by calling

    Application.Run(secondaryForm)

    in a background worker. Each of your graphs would appear in their own form, which can be undesirable. You'll also probably have to use .Invoke and .RequiresInvoke ALOT between the different forms. But it would solve your problem.

    Hope this helps!

    • Marked as answer by Kevin Chaves Monday, March 5, 2012 6:34 PM
    Wednesday, February 29, 2012 9:34 PM
  • I am unable to offer direct assistance with the 3rd party control, but I can help with coding issues?

    How many times per second would you estimate that the control is being redrawn?  You should not need to redraw it more than 20-30 times per second.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/

    Thursday, March 1, 2012 12:48 AM
  • @Idea Hat

    So I'm thinking I might be able to take a combination, each device has it's own form filled with controls, so I'm wondering if I can use Application.Run(DeviceForm) for each device they won't interfere with each other. Then maybe I can help speed up each individual modifying the refresh

    @RudeDog2

    It doesn't actually draw that often, probably once every second, when the scan is finished. The problem is it draws 5 graphs per device all at the same time with several thousands of points per scan (guestimate), its just a massive dump which causes it to stutter. Multiple devices all running together the big dump stops other devices from drawing their graphs causing the window to appear to freeze for a few seconds, and by the time the window starts responding the next set of scans start getting dumped.

    any way ZedGraph is open source now so its free to butcher up as necessary.

    Thursday, March 1, 2012 1:20 AM
  • Several thousand points.  That is what I am talking about.  Post the drawing code.  Is your code updating the display on each and every point?  That's what I am talking about.  Updating it the display too often. 

    Doesn't the control allow you to display an image?  Drawing to an image in memory is a lot faster than drawing directly on a control.  You could draw the image, then assign it to the control.  If you make a copy of the image, you could draw on top of the existing data, and then update your control again. 

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/

    Thursday, March 1, 2012 1:26 AM
  • Here's a thread that I hope starts a brainstorm.  Food for thought. 

    http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/ee843aba-f2cc-4d4a-bd67-dd2fa7df5b0c 

    Adding CrossHairs to your control.  

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/

    • Marked as answer by Lie You Monday, March 5, 2012 3:20 AM
    • Unmarked as answer by Kevin Chaves Monday, March 5, 2012 4:00 AM
    Thursday, March 1, 2012 1:29 AM
  • Actually it only refreshes when the new curve is added

                
    GraphPane myPane = this.zedGraph.GraphPane;
    myPane.CurveList.Clear();
    LineItem myCurve = myPane.AddCurve("Spectrum", this.xdata, datay, Color.Blue, SymbolType.None);
    myCurve.IsLegendLabelVisible = false;
    this.zedGraph.AxisChange();
    
    if (this.InvokeRequired)
    {
         Invoke(new refreshData(RefreshData), null);
         return;
    }
    this.zedGraph.Refresh();

    Thursday, March 1, 2012 1:35 AM
  • You didn't post the entire method, so I have to guess on the context.  But, it still looks like you are updating the control on each point, which probably means you are forcing the control to be redrawn umpteen thousands of times per second.  Your video display only updates 30 times per second, because the human eye cannot detect anything faster than that.  Try calling Refresh less often.  

    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/

    • Marked as answer by Lie You Monday, March 5, 2012 3:20 AM
    • Unmarked as answer by Kevin Chaves Monday, March 5, 2012 4:00 AM
    Thursday, March 1, 2012 1:40 AM
  • adding the curve is all done in a background thread, the only thing I left out in the SetGraphData(byte[] data) is where we do some math on it which is also in that background thread. Refresh is called in the screen redraws, it just does this for 5 graphs at the end of each scan. It doesn't sound like it should be getting nailed to the wall after the end of a scan but it is.

    /// called once per scan on each graph
    public void SetGraphData(byte[] data)
    {
        /// doo super secret math on data and make xdata
    
        GraphPane myPane = this.zedGraph.GraphPane;
        myPane.CurveList.Clear();
        LineItem myCurve = myPane.AddCurve("Spectrum", this.xdata, datay, Color.Blue, SymbolType.None);
        myCurve.IsLegendLabelVisible = false;
        this.zedGraph.AxisChange();
        RefreshData();
    }
    
    public void RefreshData()
    {
        if (this.InvokeRequired)
        {
             Invoke(new refreshData(RefreshData), null);
             return;
        }
        this.zedGraph.Refresh();
    }


    Thursday, March 1, 2012 1:59 AM
  • So I'm liking what the application.run does

    But i'm noticing that alot of our underlying connection stuff doesn't work any more, and after closing the main program each deviceform stays open which is fine but I need the main form to know a device just disconnected and is available to connect.

    Is it possible to save a list of the threads to dispose of them on exit? And call back the main form to remove disconnected devices?

    Thursday, March 1, 2012 11:41 PM
  • Hmm.  Now you are digging into the behavior of the control.  But, I have noticed something in your code that looks a bit odd.  Your RefreshData method might work better like this.  

    public void RefreshData()
    {
        if (this.InvokeRequired)
        {
             Invoke(new refreshData(RefreshData), null);
             
        }
        else 
        {
             this.zedGraph.Refresh();
        }
    }

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/

    • Marked as answer by Lie You Monday, March 5, 2012 3:20 AM
    • Unmarked as answer by Kevin Chaves Monday, March 5, 2012 4:00 AM
    Thursday, March 1, 2012 11:48 PM
  • This might be a stupid question, but is there an actual performance difference? They accomplish the same thing so its just nitpicking with coding practices.

    The if always gets checked, 

    it runs invoke then returns

    or

    it runs refresh then returns

    Friday, March 2, 2012 12:19 AM
  • Probably not.  I assume that it is being called by multiple threads, such that execution of Refresh would eventually have to take turns being invoked on the GUI thread.  Maybe it only needs to called once when all threads have completed. 

            public void RefreshData()
            {
                if ( this.InvokeRequired )
                {
                    Invoke(new refreshData(RefreshData), null);
    
                }
                else
                {
                    if ( allThreadsCompleted )
                    {
                        this.zedGraph.Refresh(); 
                    }
                }
            }

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/


    • Edited by Rudedog2 Friday, March 2, 2012 12:32 AM
    • Marked as answer by Lie You Monday, March 5, 2012 3:20 AM
    • Unmarked as answer by Kevin Chaves Monday, March 5, 2012 4:00 AM
    Friday, March 2, 2012 12:24 AM
  • So I'm liking what the application.run does

    But i'm noticing that alot of our underlying connection stuff doesn't work any more, and after closing the main program each deviceform stays open which is fine but I need the main form to know a device just disconnected and is available to connect.

    Is it possible to save a list of the threads to dispose of them on exit? And call back the main form to remove disconnected devices?

    Why would you need to save a list of threads?  If you  call them properly, then they should take care of themselves and return to the ThreadPool. Not sure if I understand your question, though.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/

    EDIT: Please start a new thread for this new question on a new topic


    • Edited by Rudedog2 Friday, March 2, 2012 12:36 AM
    • Marked as answer by Lie You Monday, March 5, 2012 3:20 AM
    • Unmarked as answer by Kevin Chaves Monday, March 5, 2012 4:00 AM
    Friday, March 2, 2012 12:35 AM
  • Application.Run()

    I created a thread and ran started the form on that thread so they'd each have their own message events to not block each other.

    And its only 1 thread that calls the refresh, it just calls it for each graph when setting the new curve.

    Friday, March 2, 2012 1:30 AM
  • And its only 1 thread that calls the refresh, it just calls it for each graph when setting the new curve.

     I realize that the same thread *executes* ZedGraphControl.Refresh, but do you realize that multiple threads are calling your RefreshData method?  Consider what the net result will be.  Let's assume 5 threads.

    1. Thread t1 finishes its' computation and builds a curve, c1, and then calls RefreshData, which draws c1.

    2. Thread t2 finishes and builds c2, and then calls RefreshData, which draws c2 and c1.

    3. Thread t2 finishes and builds c3, and then calls RefreshData, which draws c3, c2 and c1.

    4.  See the pattern yet?

    5. Thread t5 finishes and builds c5, and then calls RefreshData, which draws c5, c4, c3, c2 and c1. 

    All 5 threads invoke Refresh data independently, and at random times.  Your code will draw 15 curves, instead of just 5. 

    Rudy  -8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/

    • Marked as answer by Lie You Monday, March 5, 2012 3:20 AM
    • Unmarked as answer by Kevin Chaves Monday, March 5, 2012 4:00 AM
    Friday, March 2, 2012 8:35 PM
  • So refreshing graph A also redraws graphs b,d,c?

    There is 1 Device Form with 1 background thread with 5 graphs, for each graph will clear the curve, set a new curve, then refresh the graph

    would it be faster if I didn't call refresh on each graph but refreshed the form after it completes setting all the graphs?

    Friday, March 2, 2012 9:49 PM
  • So refreshing graph A also redraws graphs b,d,c?

    There is 1 Device Form with 1 background thread with 5 graphs, for each graph will clear the curve, set a new curve, then refresh the graph

    would it be faster if I didn't call refresh on each graph but refreshed the form after it completes setting all the graphs?

    WHOA.  I had thought you were drawing multiple curves on the same graph.  I had thought each curve was as I described, each curve is calculated on a thread and then later displayed on your graph.

    I am also ignoring all of that stuff about Application.Run.  (Did you try out the CrossHairs?)


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/


    • Edited by Rudedog2 Friday, March 2, 2012 9:57 PM
    • Marked as answer by Lie You Monday, March 5, 2012 3:20 AM
    • Unmarked as answer by Kevin Chaves Monday, March 5, 2012 4:00 AM
    Friday, March 2, 2012 9:52 PM
  • No I havn't tried crosshairs yet,  I don't really understand where it even fits into what I'm trying to do, and second whats wrong with application.run?

    If i can figure out how to let the main form know the thread died it should be easy to clean up the device and not have to worry about each devices performance interfering with another.

    Friday, March 2, 2012 10:01 PM
  • Invoking a delegate asynchronously will cause the target method to be invoked on a threadpool thread, and a callback is made to the invoking thread when the asynchronous method completes.  Imagine assigning a method to a thread, and an event is fired when the method completes.  This behavior is also wrapped up inside of the BackgroundWorker class.  

    Application.Run.  I don't even know how to call Application.Run multiple times, concurrently, in the same WinForms application.   You cannot just simply fire a thread and call Application.Run without problems.  You have ApartmentState and ExecutionContext to worry about.  A process is not supposed to have multiple main threads, anyway.

    CrossHairs.  The sample code that you could add to a test form is contained within a #region.  The second class is the CrossHairsForm class itself.  The sample draws crosshairs over a ZedGraphControl named, "zg1".  I have also noticed that the link to the sample application is now defunct.  It appears that domain name is now up for sale.  Like I said, it was food for thought, not a solution to your immediate problem.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/


    • Edited by Rudedog2 Friday, March 2, 2012 10:13 PM
    • Marked as answer by Lie You Monday, March 5, 2012 3:20 AM
    • Unmarked as answer by Kevin Chaves Monday, March 5, 2012 4:00 AM
    • Marked as answer by Kevin Chaves Monday, March 5, 2012 6:34 PM
    • Unmarked as answer by Kevin Chaves Monday, March 5, 2012 6:34 PM
    Friday, March 2, 2012 10:12 PM
  • Success! I've gotten acceptable perfomance on each device form using application run!

    The only annoying part is that it doesn't seem to be a normal thread, the main form closing doens't kill each deviceform which isn't a problem, but it would be nice if I could hook up some sort of function to close the thread when the main event closed.

    Monday, March 5, 2012 6:36 PM
  • Congratulations on your successs.

    There are two typical approaches for displaying multiple forms.  One approach is to use the Process class to start up applications independently, under the control of your application.  A simpler approach is to use a MdiParent form to display multiple child forms. 

    Happy Coding. 

    Rudy   =9^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://thesharpercoder.blogspot.com/

    Monday, March 5, 2012 10:30 PM