In this lab we will reinforce some of the concepts from Lab 1. Then we will apply these concepts to drawing a plot on one of the child windows of a Multiple Document Interface (MDI) application using the document/view architecture used in the Scribble tutorial. The data for the plot will be taken from two arrays, one may consist of several cycles of a sine wave, "x(t)", and a second array should contain time axis data. The data in the "x(t)" and "t" arrays should be mapped to the pixel coordinates on the screen where the graph is to be drawn in both the vertical and horizaontal directions. This can be done using a straight-forward linear scale factor. The plotting program should also erase any previously existing plot on the screen prior to drawing the new plot. The plot should include both vertical (x(t)) and horizaontal (t) axes with tick marks, tick labels, and axes labels. The plot should also scale in size when the window on which it is drawn is resized.
Read the following Document/View Overview from the Microsoft MSDN website. It discusses the Document class and the View class which are at the core of good Visual C++ programming technique. You will note that the Scribble tutorial is also available online. A similar document can also be viewed in the MSDN library found with the Visual C++ application.
Next read about the device context class (CDC). This is the class whose member functions are used to draw to the device context (a PC screen in our case). Pay carefull attention to CDC member functions which can be used to implement your plotting program. The above link is on the microsoft website, and a similar document also appears in the local MSDN library (found in one of the lab PC’s).
Now that you have some background, lets try doing a simple plotting program. Open Visual C++, and select "New" from the "File" menu.
void CFirst_plotView::OnDraw(CDC*
pDC)
{
CFirst_plotDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
pDC->Rectangle(0,
0, 200, 200);
}
and build the project. You will get a 200 by 200 pixel square drawn in the top left corner of the window's client area meaning that the x-coordinate increases from left to right and the y coordinate increases from top to bottom…yes, the upper left corner has device coordinates ( x= 0, y = 0) and x increases to the right while y increases downward:

Device Units: these are display pixels which are unique to a particular device. In the above example, the size of the square is 200 by 200 pixels as it appears of a 1024 by 768 pixel monitor. On a lower resolution monitor, say, 800 by 600 pixels, the size of the square would be bigger.
Logical Units: units associated with a window. The type of logical units depends on the mapping mode being used. If we use the MM_TEXT mapping mode logical units are in pixels, however other mapping modes have logical units in milimeters or inches. If the mapping mode is set to MM_ANISOTROPIC, then a good way to think of logical units is in terms of the units corresponding to the actual data we want to plot (i.e. volts in the y direction and time in the x direction). The mapping mode can be set with the CDC function "SetMapMode".
So what is the relation between logical units and device units? Any time a square (or other figure) is drawn, the Windows GDI maps from logical units (associated with a window) to device units (associated with a viewport). If we use the default mapping mode (MM_TEXT), then logical units and device units are one and the same and no scaling takes place between logical and device units. However if we use the mapping modes MM_ISOTROPIC or MM_ANISOTROPIC, the scaling does take place. Type in the following OnDraw function code:
void CFirst_plotView::OnDraw(CDC* pDC)
{
CFirst_plotDoc* pDoc
= GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
pDC->Rectangle(0,
0, 200, 200);
pDC->SetMapMode(MM_ISOTROPIC);
pDC->SetWindowExt(1000, 1000);
pDC->SetViewportExt(500,500);
pDC->Rectangle(0,
0, 200, 200);
}
and compile and run the program. The first call to "Rectangle" draws a 200 by 200 pixel square. However the "SetWindowExt" sets the range of logical coordinates to be 1000 logical units in both the x and y directions and the "SetViewportExt" sets the range of device units to be 500 in the x and y directions. This means that the size of the second square is half that of the first square since the mapping from logical to device units calls for a scaling of ˝.:

Now resizing this window still does not scale the squares in proportion to the size of the window's client area. This can be done by forcing the viewport range to be equal to the size of the window client area. The size of the window client area (i.e. the drawing area size) can be found using the CDC function "GetClientRect". Type the following OnDraw function:
void CFirst_plotView::OnDraw(CDC* pDC)
{
CRect rectClient;
CFirst_plotDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
GetClientRect(rectClient);
pDC->SetMapMode(MM_ISOTROPIC);
pDC->SetWindowExt(1000,
1000);
pDC->SetViewportExt(rectClient.right, rectClient.bottom);
pDC->Rectangle(0, 0, 200, 200);
}
After compiling and running this program you will initially see the 100 by 100 pixel square, then when you resize the window, it will scale! So if the mapping mode is MM_ISOTROPIC, we can establish the scaling that takes place between logical coordinates and device coordinates. Here’s how it works: if we have the code section:
pDC->SetWindowExt(x_winext, y_winext);
pDC->SetViewportExt(x_vpext,y_vpext);
then logical units passed to any of the CDC class drawing functions will
by scaled in both the x and y direction. The x scaling factor is given by
x_scale =
x_vpext/x_winext
while the y scaling factor is given by
y_scale = y_vpext/y_winext
So if we add the line
pDC->Rectangle(x1_logical,
y1_logical, x2_logical, y2_logical);
then a rectangle will be drawn with its upper left corner (in device coordinates) at:
(x_scale*x1_logical, y_scale*y1_logical)
and the lower right corner (in device coordinates) at :
(x_scale*x2_logical, y_scale*y2_logical)
If the mapping mode is MM_ISOTROPIC, then x_scale and y_scale must be equal. This keeps the aspect ratio the same, notice that when you resize the square in the above example, it remained a square. This is important for applications like animation. If we use the MM_ANISOTROPIC mapping mode, then it is not necessary that x_scale be equal to y_scale.
The MM_ANISOTROPIC mapping mode is useful when we are trying to fit our data entirely within our plotting window. Suppose we wish to plot our data entirely within a rectangle of width 300 and height 100 in device coordinates. Futhermore, suppose the waveform we wish to plot has an amplitude range from 0 to 1 and a time range from 0 to 100 (milliseconds, say). The following code would allow us to easily plot the data:
void CSec_plotView::OnDraw(CDC*
pDC)
{
CSec_plotDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add
draw code for native data here
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetWindowExt(100,
1);
pDC->SetViewportExt(300, 100);
pDC->Rectangle(0, 0, 100, 1);
}
Here, we have x_scale = 300/100 = 3 and y_scale = 100/1 = 100. Therefore, the rectangle is drawn with the upper left corner at (0, 0) and the lower right corner at (300, 100) in pixels (device coordinates). The resulting window looks like this:

At this point we still need to take into account the fact that our data is not necessarily going to be positive (i.e. our logical units in either the x or y direction may be negative). This can be easily handled by changing the origin of our logical coordinate system using the SetViewportOrg and SetWindowOrg CDC functions. If we call the functions
pDC->SetWindowOrg(x_winorg, y_winorg);
pDC->SetViewportOrg(x_vporg, y_vporg);
pDC->SetWindowExt(x_winext, y_winext);
pDC->SetViewportExt(x_vpext,y_vpext);
Then the mapping between logical and device coordinates is given by
x_scale =
x_vpext/x_winext
y_scale = y_vpext/y_winext
x_device =
(x_logical – x_winorg)*x_scale + x_vporg
y_device = (y_logical – y_winorg)*y_scale + y_vporg
Notice that setting x_winorg to the minimum of our logical x data and and setting y_winorg to the minimum of our y data maps those minima to (x_vporg, y_vporg). For example suppose our logical x data varies from 0 to 100 and our logical y data varies from -2 to 5. Also suppose we wish to plot the data in an area 300 pixels wide and 100 pixels high, with viewport origin at (200, 100). This would lead to:
pDC->SetWindowOrg(0, -2);
pDC->SetViewportOrg(200, 100);
pDC->SetWindowExt(100, 7);
pDC->SetViewportExt(300,100);
pDC->Rectangle(0,-2,100,
5);
Which produces a rectangle whose upper left corner is at (x = 200, y = 100) pixels:

At
this point you should have enough background to begin writing the plotting
program. Electronically
submit your code and a copy of your plot. The
sine wave data should be generated within your program (about 100 points) . Also indicate in your writeup when the OnDraw function is called.