Published
Edited
Dec 8, 2020
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const data = Array.from(reshapedDataset.get(speciesTypes))

const svg = d3.create('svg')
.attr("viewBox", [0, 0, dimensions.width, dimensions.height])


const bounds = svg.append("g")
.attr("transform", `translate(${dimensions.margin.left}, ${dimensions.margin.top})`)
// joining data to box containers
// instead of binning per shape (line, rect)
// saving browser memory
const box = bounds
.selectAll("g")
.data(data)
.join("g")

// Axis
// at bounds level not per box
bounds.append("g")
.attr("class", "axis")
.call(xAxis)
bounds.append("g")
.attr("class", "axis")
.call(yAxis)

// Whisker lines
box.append("line")
.attr("class", "whisker")
.attr("x1", ([metric, data]) => xScale(metric) + bandwidth/2)
.attr("x2", ([metric, data]) => xScale(metric) + bandwidth/2)
.attr("y1", ([metric, data]) => yScale(data.range.r1))
.attr("y2", ([metric, data]) => yScale(data.range.r0))

box.append("path")
.attr("class", "whisker")
.attr("d", ([metric, data]) => `
M${xScale(metric) + bandwidth/4},${yScale(data.range.r0)}
H${xScale(metric) + bandwidth - bandwidth/4}
`);
box.append("path")
.attr("class", "whisker")
.attr("d", ([metric, data]) => `
M${xScale(metric) + bandwidth/4},${yScale(data.range.r1)}
H${xScale(metric) + bandwidth - bandwidth/4}
`);
// rect
box.append("rect")
.attr("class", "box")
.attr("x", ([metric, data]) => xScale(metric))
.attr("y", ([metric, data]) => yScale(data.quartiles.q3)) // watch out here
.attr("width", ([metric, data]) => bandwidth)
.attr("height", ([metric, data]) => yScale(data.quartiles.q1) - yScale(data.quartiles.q3)) // here

// q2 line
box.append("path")
.attr("class", "q2Line")
.attr("d", ([metric, data]) => `
M${xScale(metric)},${yScale(data.quartiles.q2)}
H${xScale(metric) + bandwidth}
`);
return svg.node();
}
Insert cell
Insert cell
Insert cell
bandwidth = xScale.bandwidth()
Insert cell
xScale = d3.scaleBand()
.domain(features)
.range([0, dimensions.boundedWidth])
.padding(0.5)
Insert cell
yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, entrie => entrie.value)])
.range([dimensions.boundedHeight, 0])
Insert cell
Insert cell
Insert cell
xAxis = g => g
.call(d3.axisBottom(xScale).tickSize(0).tickPadding(20))
.attr("transform", `translate(0, ${dimensions.boundedHeight})`);
// d3.axisBottom(xScale).select('.tick:first-of-type text').remove()
Insert cell
Insert cell
yAxis = {
const axis = d3.axisLeft(yScale)
.tickSizeOuter(0)
.tickSize(dimensions.boundedWidth + 10)

return g => g
.call(axis)
.attr("transform", `translate(${dimensions.boundedWidth})`)
// setting class to grid line at tick value = 2 for highlighting (style section)
.selectAll(".tick")
.classed("baseline", d => d == 2);
}
Insert cell
Insert cell
html`
<style>
.axis line {
stroke: #ddd;
}
.axis .baseline line {
stroke-width: 0.6px;
stroke: #3e4989;
}
.axis .domain {
display: none;
}
.whisker {
stroke: black;
stroke-width: 1px;
}
.box {
fill: #31688e
}
.q2Line {
stroke: white;
stroke-width: 1px;
}
</style>
`
Insert cell
Insert cell
data = require('vega-datasets@1')
Insert cell
iris = data['iris.json']()
Insert cell
printTable(iris.slice(0, 5))
Insert cell
iris.length
Insert cell
features = Object.keys(iris[0]).filter(key => key !== 'species')
Insert cell
species = Array.from(d3.group(iris, v => v.species).keys())
Insert cell
Insert cell
unpivotReducer = (accumulator, record) => {
const unpivotObj = features.map(feature => ({
metric: feature,
value: record[feature],
species: record.species
}))
accumulator.push(...unpivotObj)
return accumulator
}
Insert cell
dataset = iris.reduce(unpivotReducer, [])
Insert cell
reshapedDataset = d3.rollup(
dataset,
subset => {
let aggregation = {};

subset.sort((a, b) => a.value - b.value);
const values = subset.map(record => record.value);
const min = values[0];
const max = values[values.length - 1];
const q1 = d3.quantile(values, 0.25);
const q2 = d3.quantile(values, 0.50);
const q3 = d3.quantile(values, 0.75);
const iqr = q3 - q1;
const r0 = Math.max(min, q1 - iqr * 1.5);
const r1 = Math.min(max, q3 + iqr * 1.5);

aggregation.quartiles = { q1, q2, q3 };
aggregation.range = { r0, r1 };
aggregation.values = values;
aggregation.outliers = values.filter(value => value < r0 || value > r1)

return aggregation
},
entrie => entrie.species,
entrie => entrie.metric
)
Insert cell
Insert cell
dimensions = {
let dimensions = {
width: width,
height: 400,
margin: {top: 30, right: 30, bottom: 60, left: 50}
}

dimensions.boundedWidth = dimensions.width
- dimensions.margin.left
- dimensions.margin.right

dimensions.boundedHeight = dimensions.height
- dimensions.margin.top
- dimensions.margin.bottom

return dimensions
}
Insert cell
Insert cell
Insert cell
d3 = require("d3@v6")
Insert cell
import {printTable} from '@uwdata/data-utilities'
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