Published
Edited
Mar 1, 2022
Importers
6 stars
Insert cell
Insert cell
Insert cell
data = [
{ time: 1, name: 'tom', bagels: 3 },
{ time: 1, name: 'jerry', bagels: 1 },
{ time: 2, name: 'tom', bagels: 2 },
{ time: 3, name: 'jerry', bagels: 2 },
{ time: 3, name: 'elmer', bagels: 1 },
{ time: 4, name: 'tom', bagels: 1 },
{ time: 4, name: 'elmer', bagels: 2 }
]
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
filledData = fillData2(data, ['time', 'name'], ['bagels'], {
timeField: 'time'
})
Insert cell
Insert cell
Insert cell
Insert cell
prevFilledData = fillData2(data, ['time', 'name'], ['bagels'], {
mode: 'previous',
timeField: 'time'
})
Insert cell
Plot.plot({
marks: [Plot.areaY(prevFilledData, { x: 'time', y: 'bagels', fill: 'name' })]
})
Insert cell
Insert cell
Insert cell
function fillData2(data, keyFields, valueFields, options = {}) {
// validate key fields and value fields exist
if (keyFields == undefined || keyFields.length == 0) {
throw "No key fields specified";
}
if (valueFields == undefined || valueFields.length == 0) {
throw "No value fields specified";
}
// set options
const modes = ["filler", "previous"];
const { mode = "filler", filler = 0, timeField = null } = options;
if (!modes.includes(mode)) {
throw `Mode must be either "filler" or "previous", not ${mode}`;
}
// validate options
if (mode === "previous" && !timeField) {
throw `timeField must be defined in "previous" mode`;
}
if (timeField !== null && !keyFields.includes(timeField)) {
throw `timeField must be on of the fields set in keyFields, not ${timeField}`;
}
// array-ize singular fields
if (typeof keyFields !== "object") {
keyFields = [keyFields];
}
if (typeof valueFields !== "object") {
valueFields = [valueFields];
}
// first, get all the values for each key prop
const keySets = makeKeySets(data, keyFields);
console.log("a", keySets);

// if timeField, sort that keyset
if (timeField) {
keySets.set(timeField, Array.from(keySets.get(timeField)).sort());
}

// next, make key maps from the original data
const dataKeyMap = makeExistingDataKeyMap(data, keyFields);
console.log("b", dataKeyMap);

// used to tell how many entries back in the cartesian
// product array the previous time value is
const entriesPerTime = Array.from(keySets)
.filter((d) => d[0] !== timeField)
.map((d) => d[1].size)
.reduce((r, g) => r * g);

// get the cartesian product of all keyProp values
// for those combinations that exist, use existing data
// for those combinations that don't, use filler data
const productArray = product(...[...keySets.values()]);
return productArray.map((k, i) => {
// if the entry for that key set already exists, return it
const entry = dataKeyMap.get(k.toString());
if (entry) {
return entry;
}
// if the entry does not exist, create it
if (mode === "filler") {
// in filler mode, just use the filler value(s)
return makeFillerEntry(keyFields, k, valueFields, filler);
} else {
// in previous mode, get the previous time value(s)
if (!productArray[i - entriesPerTime]) {
// if the previous time does not exist, create a new filler entry
const firstEntry = makeFillerEntry(keyFields, k, valueFields, filler);
// add it to the data key map for future times to reference
dataKeyMap.set(k.toString(), firstEntry);
return firstEntry;
}
const prevData = dataKeyMap.get(
productArray[i - entriesPerTime].toString()
);
const newEntry = makeFillerEntry(
keyFields,
k,
valueFields,
valueFields.map((d) => prevData[d])
);
dataKeyMap.set(k.toString(), newEntry);
return newEntry;
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
makeFillerEntry = (keyProps, keyValues, valueProps, filler) => {
const obj = {};
keyProps.map((d, i) => {
obj[d] = keyValues[i];
});
valueProps.map((d, i) => {
if (filler.length) {
obj[d] = filler[i];
} else {
obj[d] = filler;
}
});
return obj;
}
Insert cell
makeExistingDataKeyMap = (data, keys) => {
return new Map(data.map(d => [keys.map(k => d[k]).toString(), d]));
}
Insert cell
makeKeySets = (data, keys) => {
const keyMap = new Map(keys.map(d => [d, new Set()]));
data.forEach((d, i) => {
keys.forEach(k => {
if (d[k] !== undefined) {
keyMap.get(k).add(d[k]);
} else {
throw `key ${k} not found in row ${i} of data`;
}
});
});
return keyMap;
}
Insert cell
Insert cell
import { product } from '@mbostock/cartesian-product'
Insert cell
import { signature, code, PINNED } from '@mootari/signature'
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