A 2D Lite Graph Control with Multiple Plot Support

This is a simple, lightweight 2D graph control that supports multiple plots as well as printing. Why make another graph control? This one gives the user basic functionality which would make a great oscilloscope without a lot of extra features getting in the way. Because it is so small, it's also a great base from which to develop a more fully featured specialized graph–the very essence of OOP!

Note: It makes use of the MemDC class written by Keith Rule, and downloaded from Codeguru.com.

Overview

Once the control has been created and displayed, the user may run the mouse over it, and the current position and value at that position will be displayed in the upper left-hand corner. The values that are displayed are for the currently selected plot.

In the example above, the selected plot is the "Green Data" plot, which is a sine wave. To select another plot, click its key value on the left-hand side of the graph. This will change the statistics, position, and value accordingly.

The statistics are reported for all the data in the currently selected plot, and will not change unless the underlying data changes.

To "zoom in" on a piece of the plot, draw a rectangle with the mouse by starting in the upper left-hand corner of the desired area, holding down the left mouse button, and moving it to the lower-left hand corner of the area and letting go.

Once the rectangle has been drawn, the axes' min and max limits will be recalculated and the graph will be redrawn such that the selected area fills the entire graph area.

The user can zoom as many levels down as is realistic for the particular graph in question. The graph stores an "undo" list of zooms, so by drawing a rectangle in reverse (start at bottom right, move to top left), the most recent previous zoom will be restored.

The graph also supports the "locking" of the axes. This is useful if the user would like to update the data, but keep the limits the same as a frame of reference. This also can be used to limit the zoom in one axis only. For example, locking the y axis would effectively zoom in on a narrower and narrower section of the graph. Locking both x and y would disable the zooming feature altogether. To lock an axis, click one of the two icons in the lower left-hand corner. In this example, both are unlocked.

The user also can zoom by manually changing the limits. Clicking on the min or max (in this example, min x = -5.0, max x = 100.0, min y = -10.0, max y = 10.0) will allow the user to type in a number and update the graph.

What else is there? At this point, the plots support two different styles: "line" and "bar." Above, the "blue data" is of the "bar" style, and the "red data" and "green data" are of the "line" style.

Most parameters are configurable. Take a look at the example below to see.

Documentation

There are two classes that make up this control: CPlotData and CLinePlot. CLinePlot is the main class of the control; it contains a vector of the CPlotData class. You never need to actually manipulate the CPlotData class directly, so you will focus on the CLinePlot class.

BOOL Create(const RECT& rect, UINT uiFlags, CWnd *pwndParent,
            UINT uiID);

This is pretty much the standard constructor for a CWnd derived class.

int Add(CString szName, COLORREF crColor, enumPlotStyle nStyle,
        FLOATPOINT *pptData, UINT uiPointCount);

This adds a new plot to the control. Note the FLOATPOINT structure; it is basically the same as a POINT structure, except x and y are both floats. It returns the index of the added plot.

int Add(CString szName, COLORREF crColor, enumPlotStyle nStyle,
        std::vector<FLOATPOINT> *pvecData);

Same as above, only the data is in a vector instead of an array. It returns the index of the added plot.

void Clear();

Remove all the plots.

int Count();

Returns the number of plots in the graph.

void Print();

Sends a WYSIWYG rendering of the graph to the printer.

bool Remove(int nIndex);

Removes a plot at a specific index. Returns whether or not the operation succeeded.

void Refresh();

Forces the control to be repainted.

int Selected();

Returns the index of the selected plot.

CPlotData &operator[](UINTunIndex);

Gets a reference to the plot at a specific index. This can be used to modify a plot directly.

Examples

In the "OnCreate" or "OnInitDialog":

m_LinePlot.Create(CRect(0, 0, rcBounds.Width(), rcBounds.Height()),
                  CHILD | WS_VISIBLE, this, ID_CTRL_LINE_PLOT);

Add a plot to the control:

FLOATPOINT pData3[400];
for (ii=0; ii<400; ii++)
{
   pData3[ii].x = (float)(ii);
   pData3[ii].y = (float)(cos((float)(ii)/4.0f)*8);
}
COLORREF crColor3 = RGB ( 0, 128, 0);
m_LinePlot.Add("Green Data", crColor3, CLinePlot::LpLine, pData3,
               101);

See the included example for more details.



About the Author

Paul Grenz

Paul Grenz is a software engineer with the University of Arizona, located in beautiful, sunny Tucson. His primary focus is writing software to run the LBTO (Large Binocular Telescope Observatory). When he is not writing hardware control/image processing software, he can be found writing about himself in the third person or playing with his wonderful daughter Elizabeth.

Downloads

Comments

  • Good job

    Posted by SChepurin on 05/28/2011 09:14am

    Very good job indeed. Thank you. S.Chepurin

    Reply
  • little question

    Posted by uni_apollo on 07/17/2006 04:15am

    fist,that's a good job.my question is if i want flag some point on current ploted line with different clore or signo

    Reply
  • Blank graphic

    Posted by jongarcia on 02/23/2006 07:53am

    First of all... Great work!!! Besides that, I had a little problem with the example. I'm running W98 1024x768 ant 32bits (true color). If I move move the mouse over it for a while, the graph goes blank (no axes, no grid, no plot; just a blank area). I tried other PCs (both with XP and W98) and it always works fine. But when I use my PC the graph goes blank. I'm trying to find the problem but I've got no clue. Any idea? Thanks in advance, Jon.

    • Blank graphic

      Posted by MycroftH on 02/23/2006 04:01pm

      Hi Jon: Thanks for the kind words. I wrote and tested the graph control on windows 2000 running on a pentium 3 (if I remember correctly), but I tried to keep the drawing calls as standard as I could, and they should work the same on all versions of windows from 95 on up. It almost sounds like you are overwhelming the "onpaint" function with calls due to the high update rate. Try commenting out the refresh code which is called as you pass the mouse over the control and see if the same thing happens. If it stops, you may just need to set some kind of mutex when you are in the "onpaint" function to limit the rate at which "onpaint" is called. I'd be willing to bet that the computer you are experiencing this problem on is older (slower) than the ones which do not experience the problem. That's just my first thought on the subject. I'll mull over it some more, and if I think of something else I'll let you know.

      Reply
    Reply
  • Graph does not show?

    Posted by myhanguk on 01/11/2006 03:16am

    Hi Paul,
    First of all, thanks for providing us with this nice tool of yours! I tried to include it in a litte SDI program I'm trying to write (I'm just a novice in Visual C++), and didn't quite get it to work.
    
    Basically, I did the following:
    In the InitInstance() of my application's cpp file, I commented out all the SDI template stuff, and inserted:
    
    CPlotDlg dlg;
    m_pMainWnd = &dlg;
    
    int nResponse = dlg.DoModal();
    
    if (nResponse == IDOK){}
    
    CPlotDlg is just a simple Dialog box in which I want to display your graphs, without using your the CLinePlotTestDlg class.
    
    Then, in the OnInitDialog() part of the CPlotDlg dlg object, I put:
    
    BOOL CPlotDlg::OnInitDialog() 
    {
        CDialog::OnInitDialog();
    
       // TODO: Add extra initialization here
    	
      CRect rcClient;
      GetWindowRect(&rcClient);
      rcClient.bottom = rcClient.top + 300;
      rcClient.right = rcClient.left + 900;
      MoveWindow(&rcClient);
    
      CLinePlot m_LinePlot;
      m_LinePlot.Create(CRect(0, 0, 1, 1), WS_CHILD WS_VISIBLE, this, ID_CTRL_LINE_PLOT);
    
    WS_VISIBLE, this, ID_CTRL_LINE_PLOT);	
      m_LinePlot.MoveWindow(200, 0, rcClient.Width(), rcClient.Height());
    
    return TRUE;
    }
    
    The white background of the graph area does not appear on after create and resizing, as in the test program you provided. What am I doing wrong?
    
    Thanks for you feedback,
    
    Niko

    • Graph does not show?

      Posted by MycroftH on 02/23/2006 04:14pm

      Niko:
      
      You may want to check the coordinates that "GetWindowRect" is returning. I may be mistaken, but I think you need a call to "ScreenToClient" to translate the coordinates into the dialog's coordinate system.
      
      Also, you are declaring "m_LinePlot" as a local variable within the "OnInitDialog" function. If you do it this way, it goes away after the function ends! To use it in a dialog like this, declare it as a module level variable in the dialog's header file.
      
      Ensure that "ID_CTRL_LINE_PLOT" is defined in your resource file as well.
      
      Try this:
      
      BOOL CPlotDlg::OnInitDialog() 
      {
          CDialog::OnInitDialog();
      
         // TODO: Add extra initialization here
      	
        CRect rcClient;
        GetWindowRect( &rcClient );
        ScreenToClient( &rcClient );
      
        m_LinePlot.Create( rcClient, WS_CHILD WS_VISIBLE, this, ID_CTRL_LINE_PLOT);
      
      return TRUE;
      }
      
      Good Luck!

      Reply
    Reply
  • CPen causes exception in DrawFrameWork

    Posted by ahoodin on 02/11/2005 01:17pm

    In DrawFramework, the CPens will eventually cause a recurring exception. I fixed them in my own implementation.One fix is moving them out of DrawFramWork and making them members of lineplot to be initialized in the constructor.

    • Restoring pens and fonts properly avoids memory leak

      Posted by Ernst Lustig on 06/15/2009 10:42am

      sic!

      Reply
    • CPen causes exception in DrawFrameWork

      Posted by MycroftH on 02/23/2006 04:15pm

      Sounds good to me, as long as you destroy them in the class desructor. This is probably a better way to do it than I have implemented, since it will speed up the drawing process.

      Reply
    Reply
  • Great Graph Screen

    Posted by ahoodin on 01/19/2005 01:41pm

    I really like this control. How would one reroute messages to the lineplot control when it is on a child dialog?

    • thank you

      Posted by crycrane on 12/27/2007 09:56pm

      your help is very useful

      Reply
    • working well now

      Posted by ahoodin on 02/11/2005 01:14pm

      thanks, the messages are coming through well now. There was a transparent CWnd obstructing the graph and blocking the messages.

      Reply
    • Handling messages

      Posted by MycroftH on 01/20/2005 01:24am

      I believe if you want to respond to windows messages, you just need to add a handler. Edit the source code for the graph, right click and select "Class Wizard" from the context menu that appears, and choose the message you wish to handle. You can see an example of a message handler like this if you look at the "OnMouseMove" function in the LinePlot class.

      Reply
    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • Agile methodologies give development and test teams the ability to build software at a faster rate than ever before. Combining DevOps with hybrid cloud architectures give teams not just the principles, but also the technology necessary to achieve their goals. By combining hybrid cloud and DevOps: IT departments maintain control, visibility, and security Dev/test teams remain agile and collaborative Organizational barriers are broken down Innovation and automation can thrive Download this white paper to …

Most Popular Programming Stories

More for Developers

RSS Feeds