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

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