I have this dataset:
const dataset = [
{ date: "2022-01-01", category: "red", value: 10 },
{ date: "2022-01-01", category: "blue", value: 20 },
{ date: "2022-01-01", category: "gold", value: 30 },
{ date: "2022-01-01", category: "green", value: 40 },
{ date: "2022-01-02", category: "red", value: 5 },
{ date: "2022-01-02", category: "blue", value: 15 },
{ date: "2022-01-02", category: "gold", value: 25 },
{ date: "2022-01-02", category: "green", value: 35 }
];
And I need to create a stacked barchart. To do that I used the d3 stack() function. The result I need is this:
const stackedDataset = [
{ date: "2022-01-01", category: "red", value: 10, start: 0, end: 10 },
{ date: "2022-01-02", category: "red", value: 5, start: 0, end: 5 },
{ date: "2022-01-01", category: "blue", value: 20, start: 10, end: 30 },
{ date: "2022-01-02", category: "blue", value: 15, start: 5, end: 20 },
{ date: "2022-01-01", category: "gold", value: 30, start: 30, end: 60 },
{ date: "2022-01-02", category: "gold", value: 25, start: 20, end: 45 },
{ date: "2022-01-01", category: "green", value: 40, start: 60, end: 100 },
{ date: "2022-01-02", category: "green", value: 35, start: 45, end: 80 }
]
So the same data but with a start and end property computed by d3.
I created a function that takes in input dataset and returns stackedDataset:
export function getStackedSeries(dataset: Datum[]) {
const categories = uniq(dataset.map((d) => d[CATEGORY])) as string[];
const datasetGroupedByDateFlat = flatDataset(dataset);
const stackGenerator = d3.stack().keys(categories);
const seriesRaw = stackGenerator(
datasetGroupedByDateFlat as Array<Dictionary<number>>
);
const series = seriesRaw.flatMap((serie, si) => {
const category = categories[si];
const result = serie.map((s, sj) => {
return {
[DATE]: datasetGroupedByDateFlat[sj][DATE] as string,
[CATEGORY]: category,
[VALUE]: datasetGroupedByDateFlat[sj][category] as number,
start: s[0] || 0,
end: s[1] || 0
};
});
return result;
});
return series;
}
export function flatDataset(
dataset: Datum[]
): Array<Dictionary<string | number>> {
if (dataset.length === 0 || !DATE) {
return (dataset as unknown) as Array<Dictionary<string | number>>;
}
const columnToBeFlatValues = uniqBy(dataset, CATEGORY).map(
(d) => d[CATEGORY]
);
const datasetGroupedByDate = groupBy(dataset, DATE);
const datasetGroupedByMainCategoryFlat = Object.entries(
datasetGroupedByDate
).map(([date, datasetForDate]) => {
const categoriesObject = columnToBeFlatValues.reduce((acc, value) => {
const datum = datasetForDate.find(
(d) => d[DATE] === date && d[CATEGORY] === value
);
acc[value] = datum?.[VALUE];
return acc;
}, {} as Dictionary<string | number | undefined>);
return {
[DATE]: date,
...categoriesObject
};
});
return datasetGroupedByMainCategoryFlat as Array<Dictionary<string | number>>;
}
As you can see, the functions are specific for Datum type. Is there a way to modify them to make them works for a generic type T that has at least the three fields date, category, value?
I mean, I would like to have something like this:
interface StackedStartEnd {
start: number
end: number
}
function getStackedSeries<T>(dataset: T[]): T extends StackedStartEnd
Obviously this piece of code should be refactored to make it more generic:
{
[DATE]: ...,
[CATEGORY]: ...,
[VALUE]: ...,
start: ...,
end: ...,
}
Here the working code.
I'm not a TypeScript expert so I need some help. Honestly what I tried to do was to modify the function signature but I failed and, anyway, I would like to make the functions as generic as possible and I don't know how to start. Do I need to pass to the functions also the used columns names?
Thank you very much
from Create a generic function that creates stacked dataset using d3
No comments:
Post a Comment