Adding Graphics

visualdataset.py: 31 points


Objective

We are at last ready to add our graphical visualization! We will do this by creating a new class, VisualDataSet, which inherits from our DataSet class and overrides the display method to replace our text-based visualization with a graphical version using the picture module. Starter code for this class is provided in the visualdataset.py file.

How to Complete This Section

Your workflow for this section will be be similar to the way you created your DataSet class. Read through the VisualDataSet Class Summary to get a quick feel for what’s involved, then proceed to the Detailed Description of Methods and implement each method according to the specifications given. The following section, Test Your Work will help you determine that your class is functioning properly. Once you’re satisfied that it’s behaving as expected, the final section, Update the main menu, explains how to incorporate this new feature into your main program.

VisualDataSet Class Summary

The VisualDataSet Class is similar to the DataSet class that it inherits from, but instead of printing its values to the terminal, it displays them as a collection of vertical bars, where each bar has a height in pixels equal to the corresponding value. For example, a VisualDataSet object representing the list of values [171, 324, 62, 43, 267, 221, 360, 398, 330, 176] might display them as:

VisualDataSet Displaying 10 values

The exact appearance of the display depends on values that the user chooses when creating an instance of the object. The example above shows the output for a 600 x 400 pixel image in which each bar is 58 pixels wide.

Attributes

  • width (int): The width of the display in pixels.
  • height (int): The height of the display in pixels.
  • bar_width (int): The width in pixels of a single vertical bar.
  • bg_color (string): The color to be used for the background of our graphical display.
  • fg_color (string): The color we’ll use for our vertical bars.
  • space (int): Distance in pixels between each individual bar.

Attributes Inherited from DataSet

  • size (int): The total number of values in the dataset.
  • minimum (int): The lowest value in the dataset.
  • maximum (int): The highest value in the dataset.
  • data (list): A list containing the values themselves.

Methods

  • __init__ (extends __init__ from parent class)
  • clear_canvas (new for the VisualDataSet class!)
  • display (overrides display from parent class)

Detailed Description of Methods

This section contains the requirements for each method your VisualDataSet class will need to include, as well as some implementation suggestions. Note that you’ll need to import anything you use from other modules, like picture, DataSet, etc.

import-ant note!

Note that only one import statement has been included in the starter file, but you’ll definitely need more. Expect to import anything you use from other modules, like picture, sorts, etc.

__init__(self, width, height, bar_width)

Initialize all of the attributes to the appropriate values. Note that this includes both the attributes listed above and all the attributes we inherited from the DataSet class (hint: super is your friend here).

For our purposes, it’s convenient to group our attributes into three categories corresponding to how we will set them up.

The first group includes width, height and bar_width. These should be set to the values passed in as arguments to the corresponding parameters of __init__. This means they get chosen by whoever creates an instance of the class, and can vary from one instance to the next.

The next group consists of bg_color, fg_color and space. These will be hard-coded in the body of the __init__ method (at least for now). Please write your __init__ function to assign the following values:

  • Set bg_color to "skyblue"
  • Set fg_color to "lightgoldenrodyellow"
  • Set space to 2.

Finally, in order to call the parent object’s __init__ function, we need to know what values to use for size, minimum and maximum. These values will be calculated based on the size of the display the user requested so we optimize the use of the display window (similar to the pyramid exercise from Lab 3).

Specifically, these attributes should be calculated as follows:

  • size, representing the total number of values in our data set, should set to the maximum number of vertical bars of width bar_width that will fit in the display, accounting for the fact that there will be space pixels between each bar.
  • minimum sets the smallest value in the range of values our object will generate. It should be no smaller than 5% of the height of the entire display in pixels.
  • maximum sets the largest possible value in our dataset. It should be equal to the height of the display in pixels.

For example, in the output given at the top of the VisualDataSet Class Summary, the object has been initialized with a width of 600, a height of 400, and a bar_width of 58. Based on these values, it produces a dataset of 10 items chosen randomly between 20 and 400.

Last, but not least: once all attributes have been set, our __init__ method should use the picture module’s new_picture function to create the canvas we’ll use to display our visual data. The width and height of the new picture should match the values of the width and height attributes.

clear_canvas(self)

This method sets all the pixels in our picture to the color indicated by bg_color, and then calls picture.display() to show the image. This is very useful for creating the background of our visualization. For ideas about how to implement it, you may want to look back at the code you used to create your pyramid in Lab 3.

display(self)

We’re now ready to override the display function that you implemented in the parent class (DataSet). This method has three main steps:

  1. Begin by drawing the background of your visualization using the clear_canvas method you implemented above.
  2. Use the picture module to draw vertical bars representing each of your data values in the order they appear in self.data. (See below for more details about how this should look.)
  3. Use the input() function pause the program until the user hits the Return key. Just as in the DataSet class, we are slightly misusing the input function to create a pause effect, and not actually storing any user input.

The image you create in step 2 must meet all of the following requirements:

  • Each vertical bar should have a width equal to bar_width and a height in pixels equal to the value of the data element it represents.
  • The color of the bars should match the value of fg_color.
  • All bars should be drawn so their bottom edge is flush with the bottom of the canvas.
  • The left edge of the leftmost bar should also be flush with the left side of the picture.
  • Make sure to leave space pixels between each bar.

Note that for some choices of width and bar_width, there will be a space leftover between the end of the last bar and the right edge of the image. This is fine. If you’ve chosen the number of elements in your dataset correctly, the leftover space should be less than the width of one bar.

Don’t forget your docstrings!

Again, just a reminder that writing proper documentation is an important part of writing code (and, therefore, also part of your grade for this assignment). Remember to fill out the docstring for each method you implement.

Test your work

The function test_visualdataset_class(), which is called in the main body of visualdataset.py, is intended to run a few quick checks to verify that your VisualDataSet class is working as expected. This function has been started for you, but it is incomplete. Read through the code and comments, taking particular note of the parts labeled as TODO Items 1-5.

Check your data

Begin by following the instructions in the comments to complete the first 3 TODOs. A correct solution will:

  • Create a VisualDataSet object with width 600, height 400, and bar_width 18.
  • Use get_data to access the list of values associated with your new object.
  • Check the number of values in the list, as well as the highest and lowest value, and assign correct values to the variables actual_size, actual_lowest and actual_highest, respectively.
  • Print the list of generated values to the command line.

When these three items have been completed correctly, running python3 visualdata.py should produce terminal output similar to the following:

Expected number of values: 30
Actual number of values: 30
The lowest value should be no lower than 20.
Actual lowest value: 34
The highest value should be no higher than 400.
Actual highest value: 377
Generated values:
[94, 257, 241, 162, 145, 34, 75, 319, 46, 188, 275, 293, 377, 318, 70, 302, 35, 352, 242, 100, 278, 136, 66, 191, 259, 336, 35, 210, 329, 366]

Test the display method

Now let’s address TODO Item 4: add code to call the VisualDataSet object’s display method so you can compare it to the numerical values.

If all has gone well, this should also bring up a graphical window in your Live View window that looks something like this:

VisualDataSet Displaying 30 values

Try visualizing a sorting algorithm

Finally, test your selection_sort_demo by passing in your new VisualDataSet object instead of your DataSet object. If you have implemented it correctly, no modifications should be necessary, and you will now have a working graphical visualization that updates every time you press Enter at the command line.

Update the main menu

Now that we’ve created an amazing new feature, it’s time to add it to our menu in main.py. Go back to this file and edit your code so it prompts the user to choose between text-based and graphical visualizations. For example, you might display message like:

Please select a visualization style:
	1: Text
	2: Graphical

If the user requests a text-based visualization, your program should use the DataSet class (i.e., its current behavior).

If they ask for a graphical visualization, you should instead use an instance of the VisualDataSet class. For now, we will not ask the user to choose the details of the display: if they request the graphical visualization, you should create your VisualDataSet object with a width of 600 pixels, a height of 400 pixels, and a bar_width of 18 pixels.

A note on method

Our approach here is analogous to what we did for our text-based visualization. We use sensible default values that should work on most screens, with the understanding that we could come back later and add the ability for advanced users to customize the display.

As before, your program should continue to gracefully handle any invalid selections, and should continue to prompt the user until they choose a valid option.