In lab 10, you wrote a program to simulate an experiment in which a specified number of coins were flipped and the number of heads observed. You displayed the corresponding probability distribution in the form of a table. In this assignment, you are to illustrate the probability distribution using a column chart. For example, the probability distribution given here:
Heads P(Heads) ----- -------- 0 0.014000 1 0.089700 2 0.239300 3 0.310700 4 0.230300 5 0.099400 6 0.016600
is illustrated in this column chart:
You are to implement a function, named "drawColChart", whose purpose is to create a graphics window and draw a column chart based on the information passed into the function. Here is the function prototype:
//--------------------------------------------------------- // drawColChart // // Draw a column chart based on the specified properties. // // In Parameters: // n - number of data categories // dataName - category names // dataValue - catagory values // chartTitle - the title of the chart // xAxisTitle - the horizontal axis title // yAxisTitle - the vertical axis title // background - the background color // foreground - the foreground color // fillcolor - the fill color // yinc - vertical axis label increment // yplaces - decimal places for vertical axis labels //--------------------------------------------------------- void drawColChart(int n, string dataName, double dataValue, string chartTitle, string xAxisTitle, string yAxisTitle, int background, int foreground, int fillcolor, double yinc, int yplaces)
If ever there were a time to learn how to tackle a problem one little step at a time, this is it. To think you could write the entire function all at once before ever actually testing it would be sheer folly. I'll outline the approach I took to this problem.
The size of the window is somewhat arbitrary but I suggest that the window be no smaller than say 600 pixels wide by 400 pixels high. The minimum size is dictated by how small we can make the text and still make it readable. Locate the window so that the entire window is visible when it is displayed. For now, the world coordinate system can go from -1 to 1 in both directions. It will be reset later. The title of the graphics window is the chart title which is one of the function parameters.
This is also a good time to set up the color, the background color, and the fill style. You must clear the graphics device after making these changes.
For me, it seemed reasonable to have the origin of the column chart correspond to the origin of the world coordinate system. The height of the column chart is determined by the largest data value (we are assuming that they are all non-negative) and the width is determined by the number of data values:
In addition to the column chart itself, we also have to display the chart title and the labels and title for each axis. The amount of space, relative to the column chart, is somewhat arbitrary though we want to keep it as small as reasonably possible. On the left side of the chart we need to leave space for the vertical axis title and labels. I chose to make the width of this space equal to 20% of the width of the chart itself. I left a space equal to 5% of the width of the chart on the right just to make it look nice. I left a space equal to 15% of the height of the chart at the top for the chart title and the same amount of space at the bottom for the horizontal axis labels and title.
The percentages really depend on the size of the graphics window. The values I used seem to work well for windows as small as 600 by 400 pixels. They also work for larger windows though smaller percentages would work even better. Here is the final layout I decided on:
You will need to use the reset function to set up your graphics window based on the world coordinate system for the particular data set passed to the function.
Each axis is just a line with some tick marks. The horizontal axis extends from (0, 0) to (n, 0) with tick marks at 1, 2, 3, ..., n. The length of a tick mark must be expressed in terms of the world coordinate system. I made the length of the tick marks on the horizontal axis equal to 1% of dataMax (the height of the column chart). My tick marks start on the horizontal axis and go down.
The vertical axis extends from (0, 0) to (0, dataMax) with tick marks spaced at intervals of size yinc (a function parameter). That is, the tick marks are at yinc, 2*yinc, 3*yinc, and so on. Again the length of the tick marks must be expressed in terms of the world coordinate system. I set the length of the tick marks on the vertical axis to 1% of n (the width of the column chart). My tick marks start on the vertical axis and extend to the left.
Make sure your drawColChart function works correctly before going on. Invoke it with different sets of data and make sure the coordinate axes are drawn correctly.
The columns are centered within the intervals defined by the tick marks. That is, they are centered on n = 0.5, 1.5, 2.5, and so on. The width of the column is somewhat arbitrary. Just choose something that looks nice (not too skinny and not too fat). The height of each column is the corresponding data value.
If the column height is greater than 0 you'll need to use a floodfill operation to color the interior of the column. Since you are working with world coordinates, you'll need to add a floodFill function to your worldType class implementation. Like your drawing functions, the parameters to this function will be world coordinates and the function does nothing but invoke the graphics system floodfill function with the corresponding pixel coordinates. Generally, when performing a flood fill operation, it is a good idea to specify a point in the center of the region that is being filled.
Test your function to make sure that it draws appropriate column charts for different sets of data.
Now that you've finished the easy part, it is time to tackle the hard part. Getting all of the labels and titles in their right places requires a fair amount of effort. The horizontal axis labels should be centered within the corresponding intervals (i.e., between the tick marks just like the columns) and the horizontal axis title should be centered along the horizontal axis.
Placing text precisely requires that we know exactly how wide the text will be (in the world coordinate system). For example, the label for the first interval should be centered at x = 0.5 (the middle of the first interval). Suppose the width of the first label is given by textWidth. The label text, then, should start at x = 0.5 - textWidth / 2.0 as shown here:
The good news is that there is a function in the graphics package, named "textwidth", that tells us exactly how wide a string of text will be. The bad news is that the parameter to this function must be an old-fashioned C-string and also that the value returned by this function is the width in pixels (not in the world coordinate system).
You may remember that the string class has a function named c_str that returns the text stored in the string object as an old-fashioned C-string. However, if you look at the documentation for this function, you will find that it returns a non-modifiable version (think constant). Unfortunately, the textwidth function requires a modifiable version of the string; not a constant.
The string class also contains a function, named "copy", that copies its contents into an array of characters. To make this array of characters an old-fashioned C-string, all we have to do is to append the null character immediately after the last character in the array. (Recall that C-strings are null-terminated strings.) Here is a code fragment that will convert a string object (named str) into a null-terminated array of characters (named cstring):
char cstring; // The C-string str.copy(cstring, str.length()); // Copy characters from str into cstring cstring[str.length()] = '\0'; // Append the null character to the end
That solves one problem. The second problem is that the value returned by the invocation of textwidth(cstring) is the width of the cstring in pixels. However, the calculation of the appropriate x coordinate is done in the world coordinate system, not the pixel coordinate system. To calculate the width of the text in the world coordinate system we have to set up a proportion:
Next solve the proportion for the world width (of the text):
The width of the text in pixels is given by textwidth(cstring), the width of the screen is the maximum pixel coordinate in the x direction plus one, and the width of the world window is 1.25n (since the chart is n units long and I allowed 20% extra space on the left and 5% extra space on the right). The width of the text in the world coordinate system is given by:
In the graphics package, we display text by first inserting the text into the bgiout output stream (which is used just like any other output stream). Then we invoke the outstreamxy(x, y) function in order to tell the graphics system where we want to place the contents of the stream on the screen. The parameters to this function specify the location (in pixel coordinates) of the upper-left pixel in the graphic representation of the text. That is, the text will appear below and to the right of the specified pixel. Here is a sample code fragment:
bgiout << "Label"; // Insert some text into the bgiout stream outstreamxy(5, 5); // Display the contents of the stream at (5, 5)
The text, "Label", will be displayed below and to the right of the pixel whose coordinates are (5, 5). Since the outstreamxy function requires pixel coordinates rather than world coordinates, you will have to implement a new function in your worldType class that has world coordinates as parameters and invokes the outstreamxy function in the graphics package with the corresponding pixel coordinates.
Displaying the title of the horizontal axis presents no additional challenges.
Test your function's ability to correctly draw the horizontal axis labels and title before continuing.
Drawing the vertical axis labels presents one new challenge. Unlike the horizontal axis where the labels were string values, the labels for the vertical axis are numeric values. Before I can use the techniques described above to position these labels, I must convert them from numbers to C-strings. C includes a printf function that generates formatted output. In essence, printf means "print formatted" output to the standard output device. A slightly different form of this function (sprintf) sends the output to a C-string rather than the standard output. The sprintf function is very flexible but for our purposes now, it has three parameters:
int sprintf(char* cstring, const char* formatString, double value)
The first parameter is the variable that will contain the formatted text. The second parameter specifies how the text is to be formatted. The third parameter is the value that will be formatted. Only, the second parameter needs any further explanation. You will use one of the following format strings:
"%0.0f", "%0.1f", "%0.2f", or "%0.3f"
The % symbol in a format string represents the start of a format specifier. The "f" marks the end of the format specifier and indicates that a floating point number will be converted into formatted text. Anything that comes between the % and the f are format modifiers. The number to the left of the decimal point specifies the minimum field width and the number after the decimal point specifies the number of decimal places. For example, "%10.2f" indicates that the number will be formatted using a minimum of 10 characters and rounded to 2 decimal places. The value will be right-justified within the string with leading spaces added as padding if necessary.
The first number is the minimum number of characters that will be used. If the floating point value can not be expressed in so few characters, the formatted text will use as many characters as needed. Setting the minimum width to 0 simply means that the text representation will use as many characters as needed; no more and no less.
You will notice that one of the parameters to the drawColChart function is the number of decimal places to use for the vertical axis labels (yplaces). It rarely makes sense to display labels to more than 3 decimal places. Consequently, if the number of yplaces is greater than three, this function will display the labels to just 3 decimal places.
The vertical axis title also presents a minor problem. How do I get the text to print sideways? It turns out that this is easy. The graphics package includes a function named settextstyle that has three parameters: the font, the direction of the text, and the size (in that order). I suggest you use the following styles:
For Axis Labels: settextstyle(SIMPLEX_FONT, HORIZ_DIR, 1) For Horizontal Axis Title: settextstyle(SIMPLEX_FONT, HORIZ_DIR, 2) For Vertical Axis Title: settextstyle(SIMPLEX_FONT, VERT_DIR, 2) For Chart Title: settextstyle(SIMPLEX_FONT, HORIZ_DIR, 4)
In general, drawing the chart title presents no new problems. It should be centered horizontally above the chart.
For this particular application, however, creating the chart title does present a bit of a problem. If you look at the example chart above, you will notice that the chart title includes the number of coins used in the experiment; a value that was entered by the user. The chart title can be created as shown in this code fragment:
char text; // A C-string variable string title; // A string object sprintf(text, "Flipping %i Coins", n); title = text;
In the sprintf function, the format specifier "%i" indicates that an integer value (namely, n) will be formatted as text. The formatted number replaces the format specifier in the resulting text. If n has a value of 6 then the value of text will be "Flipping 6 Coins". The C-string value is assigned to the string variable because the drawColChart function invocation requires a string parameter (not a C-string parameter). Notice that the string class has defined a version of the assignment operator that copies the characters in the C-string on the right into the string object on the left.
I've done some of the work for you in Hmwk10.cpp which contains the prototype of the drawColChart function and a function, named "illustrate", that sets up the horizontal axis labels and all the titles. It is this utility function that actually invokes the drawColChart function. You will need to invoke the illustrate function in main just after you display the tabular results on the console window. Copy your code from Lab 10 and paste it into this file. Add your name as co-author.
Submit your project file and your C++ program file as email attachments.