Published
Edited
Jul 27, 2021
Fork of Boxplot
Insert cell
Insert cell
viewof filter_year = slider({
min: 2021,
max: 2023,
title: "Year:",
step: 1
})
Insert cell
import {slider } from "@jashkenas/inputs"
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();
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
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
styles =
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 = FileAttachment('box plot demo.json').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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more