Published
Edited
2 forks
Insert cell
Insert cell
//https://observablehq.com/@d3/d3-scalepoint
//https://www.d3indepth.com/scales/
x = d3.scalePoint(attributes, [margin.left, width - margin.right])
Insert cell
console.log("y range: " + (height - margin.bottom, margin.top) )

Insert cell
console.log("J " + d3.extent(data,d => d[origin]) )
Insert cell
Insert cell
y = {
//About Map object:
//The Map object holds key-value pairs
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
let scales = new Map();

// TODO: create a suitable scale for each attribute and add it to the map
attributes.forEach(function(attribute) {
//The origin attribute is a categorical type
if(attribute === "origin"){
//The JavaScript map set() method is used to add or updates an element to map object with the particular key-value pair. Each value must have a unique key.
scales.set(
//scalePoint creates scale functions that map from a discrete set of values to equally spaced points along the specified range.
//https://www.d3indepth.com/scales/
attribute, d3.scalePoint()
//Create a new array with the result of calling a function on every element in the array.
//https://d3-wiki.readthedocs.io/zh_CN/master/Arrays/
.domain(data.map(d => d.origin))
//Reverse the parallel coordinates start from top to bottom
.range([height - margin.top, margin.bottom]));
//.interpolator(d3.interpolateRainbow);
} else{
scales.set(
attribute, d3.scaleLinear()
// d3.extent returns [min, max] in a single pass over the input, which is particularly convenient if you wish to set a scale’s domain. it can be used with accessors that retrieve a specific property from the given objects,
//https://observablehq.com/@d3/d3-extent
//The Domain denotes minimum and maximum values of your input data.
//https://www.tutorialspoint.com/d3js/d3js_scales_api.htm
//adds a call to scale.nice, which allows to materialize tick marks above and below the extent
.domain(d3.extent(data, d => d[attribute])).nice()
//Reverse the parallel coordinates start from top to bottom
.range([height - margin.top, margin.bottom]));
//.interpolator(d3.interpolateViridis);
}
//var debug = d3.extent(data, d => d[attribute]);
//debugger;
});

return scales;
}
Insert cell
// TODO: apply the color scale from task 3
//This Map object is to set each attribute a color scale
//https://observablehq.com/@d3/sequential-scales
//https://github.com/d3/d3-scale-chromatic
//https://www.d3indepth.com/scales/

colorScale = {
let colorScale = new Map();
attributes.forEach(function(attribute){
//https://observablehq.com/@d3/d3-scaleordinal
//For discrete set
if(attribute === "origin"){
colorScale.set(
attribute,
d3.scaleOrdinal(d3.schemeTableau10)
.domain(data.map(d => d.origin))
//.interpolator(d3.interpolateRainbow)
);
} else {
// For continuous data set
colorScale.set(
attribute,
d3.scaleSequential()
.domain(d3.extent(data, d => d[attribute]))
.interpolator(d3.interpolateViridis)
);
}
});
return colorScale;
}
Insert cell
console.log("data.map(d => d.origin):" + data.map(d => d.origin));
Insert cell
Insert cell
Insert cell
Insert cell
console.log("colorAttribute: " + colorAttribute);
Insert cell
Insert cell
Insert cell
paracoords = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

// set the style of hidden data items
svg
.append("style")
.text("path.hidden { stroke: #000; stroke-opacity: 0.01;}");

// a map that holds any active brush per attribute
let activeBrushes = new Map();

const polylines = svg
.append("g")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.attr("stroke-opacity", 0.4)
.selectAll("path")
.data(data)
.join("path");
// TODO: create the polylines
//This path function return x and y coordinates of the line to draw for this each row
//https://observablehq.com/@d3/d3-line
//https://www.d3-graph-gallery.com/graph/parallel_basic.html
function path(d){
console.log("cross: "+ (d3.cross(attributes, [d], (key, d) => [key, d[key]])));
return d3.line()
//use line.defined, which sets (or reads) a function that returns true when the data point should be drawn, and false when the line should stop.
.defined(([, value]) => value != null)
(attributes.map(function(p) { return [x(p), y.get(p)(d[p])]; }))
//line.y() sets or reads the y accessor for the line
//.y(([key, value]) => y.get(key)(value))
//.x(([key]) => x(key))
//(d3.cross(attributes, [d], (key, d) => [key, d[key]]));
}
//retun a color for each path corresponding the selected color attribute and its scale
polylines.attr("stroke", d => colorScale.get(colorAttribute)(d[colorAttribute]))
.attr("stroke-opacity", 0.8)
//.attr("d", path);
.attr("d", path);

//var debug = attributes.map(d=>x(d))
// create the group nodes for the axes
const axes = svg
//SVG Group Element and D3.js
//https://www.dashingd3js.com/svg-group-element-and-d3js
.append("g")
//create many elements for each data point
//https://bost.ocks.org/mike/join/
.selectAll("g")
.data(attributes)
.join("g")
//${}: It is used to embed the value into the string
.attr("transform", d => `translate(${x(d)},0)`);

//debugger;
// TODO: add the visual representation of the axes
//Reference:
//https://www.tutorialspoint.com/d3js/d3js_axis_api.htm
//https://www.d3-graph-gallery.com/graph/parallel_basic.html
//https://observablehq.com/@d3/parallel-coordinates
//selection.each(function) to invoke the specified function for each selected element
//let test =
axes.each(function(d) {
console.log("axes argument: " + d);
console.log("axes argument: " + this);
//Add scales to the y-axis
//The map.get() function in D3.js is used to return the value for the specified key string.
//https://www.tutorialspoint.com/d3js/d3js_axis_api.htm
var y_axis = d3.axisRight(y.get(d));
//In d3, `this` refers to the current node, the current DOM element.
d3.select(this)
//Selection.call(): https://github.com/d3/d3-selection#selection_call
.call(y_axis.ticks());
})
//console.log(test)
.call(g => g.append("text")
.attr("y", 7)
.attr("x", -10)
.attr("text-anchor", "start")
.attr("fill", "currentColor")
.text(d => shortAttributeNames.get(d)))
//make stick not covered by the color
.call(g => g.selectAll("text")
.clone(true).lower()
.attr("fill", "none")
.attr("stroke-width", 5)
.attr("stroke-linejoin", "round")
.attr("stroke", "white"));
//

console.log(polylines);
function updateBrushing() {
// TODO implement brushing & linking
//The code structure and approach are taken from Assignment 2
polylines.classed("hidden", d => {
//let isHidden = false;
//The some() method checks if any of the elements in an array pass a test
//https://www.w3schools.com/jsref/jsref_some.asp
//Only check the attribute that activeBrushes store the key
//The some() method checks if any of the attribute in an array pass a test
//As long as it turn true, it will return true
let isHidden = attributes.filter(f => activeBrushes.has(f)).some ( attribute =>
activeBrushes.get(attribute)[0] > y.get(attribute)(d[attribute])
||
activeBrushes.get(attribute)[1] < y.get(attribute)(d[attribute])
);
console.log(isHidden);
return isHidden;
});
}

function brushed(attribute) {
activeBrushes.set(attribute, d3.event.selection);
updateBrushing();
}

function brushEnd(attribute) {
if (d3.event.selection !== null) return;
activeBrushes.delete(attribute);
updateBrushing();
}

const brushes = axes.append("g").call(
d3
.brushY()
.extent([[-10, margin.top], [10, height - margin.bottom]])
.on("brush", brushed)
.on("end", brushEnd)
);

return svg.node();
}
Insert cell
console.log();
Insert cell
md`## Appendix`
Insert cell
height = 500
Insert cell
margin = ({ top: 10, right: 30, bottom: 10, left: 10 })
Insert cell
data = d3.csvParse(await FileAttachment("cars.csv").text(), d3.autoType)
Insert cell
attributes = data.columns.filter(d => d !== "name")
Insert cell
shortAttributeNames = new Map(
Object.entries({
mpg: "MPG",
cylinders: "CYL",
displacement: "DPL",
horsepower: "HP",
weight: "WGT",
acceleration: "ACL",
year: "YEAR",
origin: "OGN"
})
)
Insert cell
import { select } from "@jashkenas/inputs"
Insert cell
d3 = require("d3@5")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more