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

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