Published
Edited
Jun 17, 2020
Importers
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
grouped = group(irisFlower, d => d.class)
Insert cell
group(irisFlower, d => d.class, undefined, true)
Insert cell
Insert cell
group(irisFlower, d => d.class, {
length: data => data.length,
sumSepalLength: data => data.reduce((acc, cur) => +cur.sepalLength + acc, 0),
sumSepalWidth: [d3.sum, v => v.sepalWidth],
sumPetalLength: data => d3.sum(data, v => v.petalLength),
})
Insert cell
Insert cell
group(irisFlower, d => d.class, {
label: (data, accessor, group) => `${group.key} (${data.length} samples)`,
})
Insert cell
Insert cell
wrapped = wrap(irisFlower, 'seriesKey')
Insert cell
Insert cell
normalizedData = normalize()
.key('measure')
.value('size')
.over(measureKeys)
(irisFlower)
Insert cell
Insert cell
Insert cell
pivotByMeasure = pivot()
.series('measure')
.category('class')
.value(d => d.size)
(normalizedData)
Insert cell
Insert cell
pivotByClass = pivot()
.series('class')
.category('measure')
.value(d => d.size)
.aggregate(d3.mean)
(normalizedData)
Insert cell
Insert cell
Insert cell
pivotStack().category(measureKeys).series(d => d.class)(pivotByClass)
Insert cell
Insert cell
Insert cell
{
const g = group(irisFlower, d => d.class);
const norm = normalize().key('measure').value('size').over(measureKeys);
return g.map(d => ({ ...d, values: norm(d.values) }))
}
Insert cell
// The stackedBar just needs to take canonical data, series on the outside, categories on the inside...
// Clumsy way of doing that with this data is to norm, pivot, norm... so need a better rollup function.
canonicalRollup = {
const norm = normalize().key('measure').value('size').over(measureKeys);
return pivotByClass.map(d => ({ key: d.class, value: norm([d]) }))
}

Insert cell
stack().category(d => d.measure).value(d => d.size)(canonicalRollup)
Insert cell
// TODO: Works with pivoted input or nested, just needs a value key (identity for a straight pivot, d => d.value for default grouped)
// Stack is a riff on d3.stack that instead of requiring input data organized by category, has the data organized in canonical form with the outer array being the series and inner array being categories. If using data grouped or rolled up in canonical form the requires no extra work, but if not may need extra series and seriesData accessors defined.
// It also provides a "series" key on the outer array and a "category" key on the inner for easy access to keys.
function stack() {
const slice = Array.prototype.slice;

let series = d => d.key,
seriesData = d => d.value,
category = d => d.key,
value = d => d.value,
order = d3.stackOrderNone,
offset = d3.stackOffsetNone;
function stack(data) {
const m = data.length,
categories = nestedCategories(data, category, seriesData),
tuple = d => [category(d), d],
n = categories.length,
sz = new Array(m);
let i, j, oz;
for (i = 0; i < m; ++i) {
const si = sz[i] = new Array(n),
diMap = new Map(seriesData(data[i]).map(tuple));
for (j = 0; j < n; ++j) {
const dij = diMap.get(categories[j]);

si[j] = [0, +value(dij, j, data[i])];
si[j].category = categories[j];
si[j].data = dij;
}
si.series = series(data[i], i, data);
}
for (i = 0, oz = order(sz); i < m; ++i) {
sz[oz[i]].index = i;
}
offset(sz, oz);
return sz;
}
stack.series = function (_) {
return _ === undefined ? series : (series = _, this);
}
stack.seriesData = function (_) {
return _ === undefined ? seriesData : (seriesData = _, this);
}
stack.category = function (_) {
return _ === undefined ? category : (category = _, this);
};
stack.order = function (_) {
return _ === undefined
? order
: (order = _ == null ? d3.stackOrderNone : typeof _ === 'function' ? _ : constant(slice.call(_)), this);
};
stack.offset = function (_) {
return _ === undefined
? offset
: (offset = _ == null ? d3.stackOffsetNone : _, this);
};
stack.value = function (_) {
return _ === undefined ? value : (value = typeof _ === 'function' ? _ : constant(+_), this);
};

return stack;
}
Insert cell
// TODO: Should have a total function that takes canonical form data and pushes an item (or items) onto the array containing a rollup of the data... Maybe this could be more like wrap - injects the totals into an ouer object? Or just returns a single object that the user can deal with as required? What about nested categories?
// Can potentially return a total row that provides the same aggregate function over multiple fields

// TODO: Clean up functions, fix dependents in other notebooks, write total functions, revist other functions here over time
// Checked: group, wrap, normalize, all utilities
// Still to go: pivot, pivotStack (necessary?), stack
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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