- Part 1: Drawing the Data
- Part 2: Drawing the Axes
- Part 3: Interaction
- Extra Credit
In this assignment, you will write code to build an interactive parallel coordinates visualization for the Iris Dataset used in the last assignment. Your main focus will be on designing interactions that support analysis with parallel coordinates.
This assignment is designed to provide additional practice with more advanced uses of brushes and callback handlers in d3. Specific objectives include:
- Implementing a parallel coordinates view suitable for viewing multi-dimensional 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.
In this assignment, you will create a visualization that uses a parallel coordinates display to investigate the Iris dataset. Recall that this dataset has four quantitative attributes (sepal length, sepal width, petal length, petal width) and 1 categorical attibute (species). There are 50 samples in each of 3 species (setosa, versicolor, and virginica), for a dataset with 150 total flowers.
Your parallel coordinates view should draw all four dimensions simultaneously, and allow the user to investigate how the data spans the four 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 this assignment, you should get 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’ll note in the template code provided, I’ve included code that initializes, one per each dimension, a element of the
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 four points, one point per the four 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
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) 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] returns the scale associated with
dim and as a result if you call
yScales[dims](datum[dims]) it will return the actual \(y\)-coordinate of a particular
datum for dimension
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
Besides using paths, you should set the stroke color to be define by the
species of each data element, and the opacity to be 0.75. 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
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
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:
See this link for more information about the specifics of
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
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. The array
dimNames stores a string for each dimension’s label text. You’ll also want to make sure each text label is associated with the
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 two interactions.
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:
xScale, specifically you will change its domain
- Rebind the data for axes/labels/brushes, and redefine their transforms using the updated
- Rebind the data for the data paths, and update the positions of all polylines, again using the updated
All of the above should be transitioned using a duration of 1 second for each click.
For our second 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 A07, 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,
 is the minimum of the range and
 is the maximum. So, for a given dimension
brushRanges[dim] is the minimum \(y\) (in pixels) and
brushranges[dim] is the maximum \(y\) (in pixels).
isSelected() function should check all dimensions, and for an input data element
d is within the range of each brush. If an axis’s brush is turned off,
brushRanges for that dimension will be
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.
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:
d3.jsplus any others you require)
.cssfiles containing style information
|Bugs or syntax errors||-10 each bug at grader's discretion to fix|
Point Breakdown of Features
|Consistent modular coding style||5|
|External documentation (README.md) following the template provided in the base repository||5|
|Header documentation, Internal documentation (Block for functions and Inline descriptive comments). Wherever applicable / for all files||10|
|Expected output / behavior for your chart based on the assignment specification, including|
Cumulative Relationship to Final Grade
Worth 5% of your final grade
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 visual interface to show other statistical information per axis
- Generally improving upon the visual look-and-feel of parallel coordinates. You may find inspiration from Kai Chang’s Parallel Coordinates and Parallel Coordinates on Observable as some examples of how to customize parallel coordinates further. If you find other examples from the web, be sure to appropriate reference them in your README.