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.

Comments
Good job
Posted by SChepurin on 05/28/2011 09:14amVery good job indeed. Thank you. S.Chepurin
Replylittle question
Posted by uni_apollo on 07/17/2006 04:15amBlank graphic
Posted by jongarcia on 02/23/2006 07:53amFirst 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.
-
ReplyBlank graphic
Posted by MycroftH on 02/23/2006 04:01pmHi 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.
ReplyGraph does not show?
Posted by myhanguk on 01/11/2006 03:16amHi 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-
ReplyGraph does not show?
Posted by MycroftH on 02/23/2006 04:14pmNiko: 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!ReplyCPen causes exception in DrawFrameWork
Posted by ahoodin on 02/11/2005 01:17pmIn 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.
-
-
ReplyRestoring pens and fonts properly avoids memory leak
Posted by Ernst Lustig on 06/15/2009 10:42amsic!
ReplyCPen causes exception in DrawFrameWork
Posted by MycroftH on 02/23/2006 04:15pmSounds 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.
ReplyGreat Graph Screen
Posted by ahoodin on 01/19/2005 01:41pmI really like this control. How would one reroute messages to the lineplot control when it is on a child dialog?
-
-
-
Replythank you
Posted by crycrane on 12/27/2007 09:56pmyour help is very useful
Replyworking well now
Posted by ahoodin on 02/11/2005 01:14pmthanks, the messages are coming through well now. There was a transparent CWnd obstructing the graph and blocking the messages.
ReplyHandling messages
Posted by MycroftH on 01/20/2005 01:24amI 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