Homework 2: Plotting Example Data
In this step, you will use a Turtle
object to plot a small data set.
The resulting plot will be added to a rectangular patch by using your rect
function from the previous step.
Implementation Steps
Setup and Reading Input Data
You’ll be writing the code for this part, the function plotExampleData()
.
Constant Definitions
Your function should start by declaring the same constants you used in the previous part, and adding this line to the stitch-size constants:
constexpr int PLOT_STITCH_SIZE = 1; // finer stitches for the plot itself
as well as the following new constants section:
// Data file parameters:
constexpr int NUM_DATA_POINTS = 59; // file has *exactly* this many points
const string input_name = "/cs70/data/stats/us_population_growth.txt";
const string output_name = "example_data.dst";
If we have the same constants in two functions, isn't that duplicated code and bad style?
In principle, we can change the values of these constants to be different in the different functions. If, when you're done, you have a constant that is the same everywhere, you can make just one global constant outside of all the functions if you like. But mostly don't worry about it, as the benefit of named constants outweighs the duplication.
Hay! Why are the integer constants
constexpr
but the strings are justconst
? What's up with that?Full-blown C++
std::string
objects are complicated things, and creating one is something that can only be done when the program actually runs. Because it can't be figured out by the compiler ahead of time, it can't be a compile-time constant.There are alternatives to
std::string
that can be figured out at compile time, but we won't go down the rabbit hole of exploring them.
Declaring the Array
Create a statically sized array of float
s that is big enough to hold NUM_DATA_POINTS
values.
Reading Values Into the Array
We want to read NUM_DATA_POINTS
values from the file INPUT_NAME
into your array of floats.
The data set you will plot in this part of the assignment is a text file with one floating-point value per line. The values represent 59 years of US population growth.
To read values from the text file, you will need to create an ifstream
(short for input file stream) object. You can use the parameterized constructor for the ifstream
class that takes one argument: a string representing the name of the file to read from. The syntax is
ifstream inputFile{"mytextfile.txt"};
Of course, you should replace inputFile
and "mytextfile.txt"
with the variable name you prefer and the name of the file you want to read, respectively. Once you have your ifstream
object, the >>
operator will read one value at a time from the file. For example, if we have a float
called myFloat
, the following line would read the first float
from the file and store it in myFloat
:
inputFile >> myFloat;
Once you have read all of the values from the file, you should call the close
member function of your ifstream
object to let the system know that you are done reading from the file.
Error Checking
When you read the file, you should make sure that the file was opened successfully and that each read operation succeeded. If any of these operations fail, you should print an error message to cerr
and return from the function without doing anything else. Useful member functions for this purpose in the ifstream
class include:
inputFile.eof()
— returnstrue
if the end of the file has been reached,false
otherwiseinputFile.fail()
— returnstrue
if the last operation failed,false
otherwiseinputFile.good()
— the opposite offail()
; returnstrue
if the last operation succeeded,false
otherwise
Generally speaking, end-of-file is only detected after a read operation fails because it tried to read past the end of the file. So you should check for both eof()
and fail()
after each read operation. You could just check for fail()
, but checking for eof()
first allows you to print a more specific error message if the file is too short.
You should also check fail()
after opening the file to make sure it opened successfully (there is also a function is_open()
, but using fail()
should be sufficient in this case).
Normalizing by the Largest Value
When you plot the data, you’ll want it to be appropriately scaled when visualized in your patch, which requires you to normalize the data by its largest value. You’ll also want to exclude the rectangular border for the patch from the available visualization space.
Find the Largest Value
Implement a loop-based approach to finding the largest float
in your array.
Generate Normalized Data
Since the height of your patch is fixed, we need to ensure that all of the data points we plot will fit on the patch. To do that, we will normalize all of the values so that they fall between 0
and the largest available y
-axis value.
- First, calculate the
availableHeight
on the patch—thePATCH_HEIGHT
minus the size of the edge stitching,EDGE_STEP
. - Create a new statically sized array of
float
s to store the normalized data values. - Scale each float in your array of data values so that it falls between
0
andavailableHeight
.
Plotting the Points
Now you’ll use the Turtle
to draw a border for the plot and plot the data.
Draw the Border
Create a Turtle
object and draw a satin-stitched rectangle that is PATCH_WIDTH
wide, PATCH_HEIGHT
tall, and has a step size of EDGE_STEP
. Be sure to use your rect
function!
Plot the Line
Generate a line plot with the values from the normalized array of floats:
- First, calculate how much width is available for each data point, and store that number in a variable. You will use that value to calculate the
x
position for each point in your plot. The total available width for all the points isPATCH_WIDTH - EDGE_STEP
. - Get ready to plot: Lift the pen (so you can jump to the first plot point) and set the
Turtle
’s step size toPLOT_STITCH_SIZE
; satin stitch should still be on. - Loop through the values in your normalized array of floats. For each, have the turtle
gotopoint(x, y)
wherex
is calculated from the available width for each point andy
comes from the array of scaled floats. You’ll likely want the points to start at a slight offset to the right so as not to overlap the rectangle border. It may take some trial and error to get the line plot roughly centered inside the rectangle.
Note: Be sure that after the Turtle
goes to the location of the first data point, the pen is down. Otherwise, you won’t see any of the points you plot! Also, these directions assume the Turtle
’s position at the end of rect()
is the bottom left corner of the rectangle.
Annotate the Plot
We can add labels to interesting points in our plots. For this step, label the peak in the middle of the plot with the year it corresponds to, 1992
, by using gotopoint()
and choosing a point to write the text. Aim to have the beginning of the first 9
of 1992
aligned vertically with the middle peak; you'll have to do some experimentation.
You can view our example patch below in the next step.
Save the Plot to a File
Save the resulting embroidery pattern to example_data.dst
. Then convert the pattern to example_data.svg
using embconv
and confirm that it looks right. When you are done, your patch should look something like
Helpful Hints
What were those tips that were on the previous page? I think they still apply!
Don't Forget to Recompile (and Reconvert with embconv
!)
Remember that every time you change embroidery.cpp
, you need to recompile it, relink it, run ./maker
again, and regenerate the .svg
file with embconv
before you can see the results. (If you fix a bug in the code and the output image doesn’t change, you probably forgot one of these steps.)
Pay Attention to Warnings (from clang++
and cpplint
)
We recommend that you fix compiler warnings immediately, rather than waiting until the end. Even if most warnings are about issues that don’t cause trouble in practice, sometimes warnings reflect very serious errors in your code!
Similarly, cpplint
is pretty picky about how you write your code, so your life will be better if you occasionally run it and correct the formatting as you go, rather than waiting until the end to fix dozens (or for larger programs, hundreds) of small annoying errors.
Want to Look at the Provided Data?
The data files are located on the server in /cs70/data/stats/
. If you want to take a look at them to make sure you are correctly reading data, you can use the cat
command (short for ‘concatenate’). This command concatenates the contents of all the files specified in its arguments and dumps the result to the terminal. So, on the server command line, you can try cat /cs70/data/stats/us_population_growth.txt
Other Useful Unix Commands
Some other useful Unix commands for looking at files on the command line are:
head
— view the first few lines of a filetail
— view the last few lines of a filemore
— view a file pausing at each screenfulless
— likemore
, but with more features.
Feel free to try them out to see what they do; for example, run
head /cs70/data/stats/us_population_growth.txt
tail /cs70/data/stats/us_population_growth.txt
more /cs70/data/stats/us_population_growth.txt
Another very useful command is man
, which displays a “manual page” for the command you specify. So, for example, man man
would show you the manpage for man
itself, and man more
shows you the manpage for more
. Manpages show you what options and arguments a command takes, what they do, and sometimes include examples of how to use the command.
(When logged in, completion status appears here.)