In this assignment, you will write code to build an interactive parallel coordinates visualization for multidimensional datasets such as those used in the last assignment. Your main focus will be on designing interactions that support analysis with parallel coordinates.

Please click here to create your repository

Objectives

This assignment is designed to provide experience with alternate visualizations of multidimensional data using parallel coordinates. In addition, it offers additional practice with more advanced uses of brushes and callback handlers in d3. Specific objectives include:

  • Implementing a parallel coordinates view suitable for viewing multidimensional datasets
  • Experimenting with using SVG path types for drawing polylines.
  • Practicing interactions with a parallel coordinates plot, particularly through implementing click events for selection and d3.brush’s for brushing and linking.
  • Designing callback handlers that support the above interactions of selection and filtering
  • Understanding the types of tasks that parallel coordinates are suitable for.

Description

In this assignment, you will create a visualization that uses a parallel coordinates display to investigate the Auto MPG dataset. This dataset has 8 attributes that describe the performance of cars. The first attribute is the name of the car and the remaining 7 attributes describe automotive characteristics such as mpg, cylinders etc. The dataset I’ve included in .js format has 392 unique data items, as I’ve cleaned the data to remove any instance of a car with incomplete values.

Your parallel coordinates view should draw all seven numeric dimensions simultaneously, and allow the user to investigate how the data spans the seven dimensions through interactions. Specifically, your visualization must support: (1) brushing on any axis to select and filter a subset of the data and (2) reordering the axes by clicking on them which should swap them with their neighbor on the right. In addition, when you mouseover an axis label, it should bold the label and change the color of all polylines to reflect a color scale designed for that axis. To organize these interactions, you’ll implement callbacks for click and mouse events. You will also make heavy use of the class attribute for many of the objects you create.

In this assignment, you should also get more comfortable with doing two types of selections and data joins: those that are mapped to the actual data array (called data in the template) and those that are mapped to an array of dimensions (called dims). The array of dimensions will be used heavily in this assignment, and we will be a one-to-one mapping between dims and many different visual elements. You should take inspiration from A02 on how to define dims similar to how the array attribs was created in A02.

You’ll note in the template code provided, I’ve included code that initializes, one per each dimension, a element of the yScale, colorScale, axes, brushes, and brushRanges arrays. You are welcome to customize this initialization further if you like, but the basic version I’ve provide should be sufficient.

Part 1: Drawing the Data

The first of four selections that you need to complete involves drawing one SVG path element per data element. These should be draw as a polyline with seven points, one point per the seven quantitative dimensions of the data element. To do so, we will use d3.line(), which accepts an array of pairs [[x1,y1], [x2,y2], ...] and returns the associated SVG path attribute d. Your first task is to write the logic that makes use of the pre-defined scales to compute this parameter.

Notably, I have provided both xScale and yScales to do this. xScale, which is of type d3.scalePoint(), will return for any element of dims the appropriate \(x\)-coordinate. For example xScale(dims[0]) will return the \(x\)-coordinate of the first (leftmost) dimension. yScales is a tuple of scales, one per dimension, that can be used to access the \(y\)-coordinate of specific data elements on a particular scale. For example, yScales[dims[0]] returns the scale associated with dim[0] and as a result if you call yScales[dims[0]](datum[dims[0]) it will return the actual \(y\)-coordinate of a particular datum for dimension dims[0].

I recommend looking at some examples of parallel coordinates for examples of how one uses d3.line(). For example, see Jason Davies’ Parallel Coordinate block and this example of Most basic parallel coordinates chart in d3.js. See this link for more information about how to use d3.line().

After you get comfortable with how these are structured, you should take a look at Kai Chang’s Parallel Coordinates and Parallel Coordinates on Observable for some examples of how to customize parallel coordinates further. Like in A02, these examples are provided for inspiration, but I expect your implementation will result in something different.

Besides using paths, you should set the stroke color to be define by using color scales. Initially, you should draw each data element using a color scale of your choice defined by the first data attribute. As you’ll see in Part 3, by using mouseover interactions you should be able to select which data attribute defines the color scale in the plot.

You should also be sure to set the class of each path to datapath for easy access in interactions.

Part 2: Drawing the Axes

To draw the axes, you will complete the other 3 selections. Specifically, you will need a selection that maps from dims to SVG groups (g) that will contain the axes. Each axis group should be given the class axis.

To draw the axes, you will need to both appropriately transform the group as well as make sure you call the appropriate axis function that we initialized. For the transforms, you should translate each axis by an \(x\)-value corresponding to its xScale.

To call the axes functions, we will use a d3 trick, using the function .each() so that we can call the appropriate axis function. Your .each() functions should look something like this:

.each(function(d) {
  return d3.select(this).call(axes[d]);
})

See this link for more information about the specifics of .each() vs. .call()

Here, d3.select(this) returns the current selection, and then we call the appropriate axis function axis[d] for that selection. .each() allows for anonymous functions so that we can select per datum d.

Besides calling the axes functions, you will also have to create text labels above each axis using the class label. This should be created similarly, but you’ll transform them to be centered above each axis. If you’d like to use more human-readable axes labels, one way to do so is to create a separate array, dimNames, stores a string for each dimension’s label text. Then when you do your data join for the labels, you index each datum d against it. The labels in dims are sufficient for this assignment. You’ll also want to make sure each text label is associated with the onClick() and onMouseOver() functions.

Finally, for each axis we will create a brush group, given the class brush. Like with axes, you’ll use .each() to call the appropriate brush function that we created in the initialization. And, like with axes, you’ll have to transform this to the appropriate location to align with the axis.

For all three of this selections, you may want to do you data join using keys so that you can associate each axis/label/brush with the name of the dimension rather than the dimension’s index in dims. This will be necessary since we will reorder the axes by reordering the array dims, so we’ll track columns by attribute name rather than index.

Part 3: Interaction

You will implement three interactions.

Mouse click

First, when a text label is clicked, you should swap it with the text label that is immediately to its right. If the rightmost label is clicked, it should swap with the one immediately to its left.

To do so, you should swap the location of the dimension in the dims array. After doing this, you can:

  1. Rebuild xScale, specifically you will change its domain
  2. Rebind the data for axes/labels/brushes, and redefine their transforms using the updated xScale
  3. Rebind the data for the data paths, and update the positions of all polylines, again using the updated xScale

All of the above should be transitioned using a duration of 1 second for each click.

Mouse Over

For our second interaction, when the mouse is moved over a text label, you should update which color scale is used to draw the data items. To do this, you will use the onMouseOver callback. Luckily, you do not have to adjust the dims array for this. Instead, you can simply look at which dim was passed into the callback, and you can do a data join on the datapaths and set the appropriate color scale from colorScales. You should also bold the label of the column whose color scale is active, which can be achieved by setting all labels to be normal font weight, and then using filters to select the active label and setting its font weight to bold.

Like for mouse clicks, you should apply a transition so that the user can see the color scale updating.

Brushing

For our third interaction, the user should be able to brush over a particular axis and this will select a subset of the data. If the user brushes over multiple axes, the selection must satisfy all selected ranges.

We will implement this similar to A02, by defining a function isSelected() which should return true for any data element that is within the range. The brushes we created are 1-dimensional, using d3.brushY(), which means that their selected ranges are a bit simpler. Specifically, [0] is the minimum of the range and [1] is the maximum. So, for a given dimension dim, brushRanges[dim][0] is the minimum \(y\) (in pixels) and brushranges[dim][1] is the maximum \(y\) (in pixels).

Your isSelected() function should check all dimensions, and for an input data element d, return true if d is within the range of each brush. If an axes’s brush is turned off, brushRanges for that dimension will be null.

All selected elements should be set to have an opacity of 0.75, whereas elements that are not selected should have an opacity of 0.1. If no brushes are enabled, then all elements should have opacity of 0.75.

Written Questions (worth 25/90 points)

Each written question should be answered with a brief paragraph (approximately 100 words or less) and appear below your parallel coordinates plot in index.html. I will not read extra material if I deem the answer too long.

  1. Provide an action,target pair to describe one possible task for each of the three interactions you were asked to implement in this assignment. Be explicit on both the action and the target. As you might imagine, these interactions could achieve multiple tasks, you only need to describe one task for each interaction.

  2. Explain, using design principle(s) and data abstraction concepts, how you chose the color scale you used for this assignment.

  3. Now that you’ve implemented both scatterplot matrices and parallel coordinates, describe (a) a situation in which you would prefer scatterplot matrices and (b) a situation in which you would prefer parallel coordinates. Provide a brief justification for both.

Submission

You should use git to submit all source code files. The expectation is that your code will be graded by cloning your repo and then executing it within a modern browser (Chrome, Firefox, etc.)

Please provide a README.md file that provides a text description of how to run your program and any parameters that you used. Also document any idiosyncrasies, behaviors, or bugs of note that you want us to be aware of.

To summarize, my expectation is that your repo will contain:

  1. A README.md file
  2. A index.html file
  3. An a03.js file
  4. All other Javascript files necessary to run the code (including cars.js and d3.js plus any others you require)
  5. Any .css files containing style information

Grading

Deductions

Reason Value
Bugs or syntax errors Up to -10 each bug at grader's discretion to fix


Point Breakdown of Features

Requirement Value
External documentation (README.md) following the template provided in the base repository 5
Consistent modular coding style, indentation, etc. 5
Header documentation and internal documentation (Block for functions and Inline descriptive comments). Wherever applicable / for all files 5
Expected output / behavior based on the assignment specification, including

Implementing the data drawing discussed in Part 110
Properly adding groups for the axes, labels, and brushes in Part 210
Implementing the swap interaction. For full credit, your swap should also swap any enabled brushes and include transitions10
Implementing color scales and updating which color scale is used based on the mouseover event10
Correctly selecting data and updating its opacity using brushes10

50
Written Questions

Written Question 15
Written Question 210
Written Question 310

25
Total 90


Cumulative Relationship to Final Grade

Worth 9% of your final grade

Extra Credit

Implementing features above and beyond the specification may result in additional extra credit, please document these in your README.md.

Notably, you may want to consider interactions that include:

  • Inverting axes (e.g. switching their minimum and maximum)
  • Using a drag-and-drop interface to reorder the axes instead of clicking to swap
  • Augmenting the visualization to include any non-numeric attributes (e.g. the automobile name)
  • Augmenting the visual interface to show other statistical information per axis
  • Generally improving upon the visual look-and-feel of parallel coordinates. If you find other inspirational examples from the web, be sure to appropriate reference them in your README.