'How can I aggregate/pivot this non-numeric data to show in a chart?
I'd like to show some data on a line chart. The source data looks like this:
[
{
date: 1,
depth: 1,
river: 'trent',
},
{
date: 1,
depth: 7,
river: 'severn',
},
{
date: 2,
depth: 5,
river: 'severn',
},
{
date: 2,
depth: 3,
river: 'trent',
},
]
I'd like the chart to:
- have date as an x axis (1-2)
- have depth as the y axis (1-7)
- have two lines, one for each river ("severn" and "trent")
So, I need to aggregate ("pivot"?) each data point based on the name of the river.
[
{
date: 1,
'trent.depth': 1,
'severn.depth': 7,
},
{
date: 2,
'trent.depth': 5,
'severn.depth': 3,
},
]
I know some combination of map
, filter
and reduce
to do this, but I'm finding it a little mind-bending.
What's the best way to do this, firstly as a one off, and then in a general, repeatable way that isn't specific to river depth over time, where I might pass in the following as arguments to a function:
- the source data (as above)
- the axis that should be the
- one (or more!) y axis as data series
- one (or more!) axes to break down/aggregate data by
For context, I'm using recharts to display my data if that matters.
So far I've got something like:
const prepareChartData = (sourceData, xAxis, yAxes, aggregateBy) => {
return entityData.map((row) => {
// recharts expects x axis to be called 'name'
const point = {
name = row[xAxis]
}
// fill in remaining data
yAxes.forEach((axis) => {
if (!aggregateBy?.length) {
point[axis] = row[axis]
} else {
// for each breakdown field
aggregateBy.forEach((breakdown) => {
// all the values this breakdown field holds
const uniqueVals = new Set(...entityData.map((row) => row[breakdown]))
// for every single value in the breakdown...
for (const breakdownVal in uniqueVals) {
point[`${breakdown}.${}`] = // ???????
}
})
}
})
return point
})
}
It's a long way from working obviously.
For second-order aggregations, input data like:
[
{
date: 1,
depth: 1,
river: 'trent',
county: 'south',
},
{
date: 1,
depth: 7,
river: 'severn',
county: 'north',
},
{
date: 2,
depth: 7,
river: 'trent',
county: 'south',
},
{
date: 2,
depth: 5,
river: 'severn',
county: 'north',
},
{
date: 2,
depth: 3,
river: 'trent',
county: 'north',
},
]
Should give an output like:
[
{
name: 1,
'trent.south.depth': 1,
'severn.north.depth': 7,
},
{
name: 2,
'trent.north.depth': 3,
'trent.south.depth': 7,
'severn.north.depth': 5,
},
]
Solution 1:[1]
You can do something like this for your data
const data = [{
date: 1,
depth: 1,
river: 'trent',
},
{
date: 1,
depth: 7,
river: 'severn',
},
{
date: 2,
depth: 5,
river: 'severn',
},
{
date: 2,
depth: 3,
river: 'trent',
},
]
const result = Object.values(data.reduce((res, {date, depth, river}) => {
const existing = res[date] || {date}
res[date] = {...existing, [river+'.depth']: depth}
return res
}, {}))
console.log(result)
const prepareChartData = (sourceData, xAxis, yAxes, aggregateBy) =>
Object.values(sourceData.reduce((res, item) => {
const key = item[xAxis]
const existing = res[key] || {[xAxis]: key}
return {
...res,
[key]: {
...existing,
...Object.fromEntries(yAxes.flatMap((valueKey) =>
aggregateBy.map(prefixKey =>[[item[prefixKey], valueKey].join('.'), item[valueKey] ] )
))
}
}
}, {}))
const data = [{
date: 1,
depth: 1,
river: 'trent',
length: 12
},
{
date: 1,
depth: 7,
river: 'severn',
length: 13
},
{
date: 2,
depth: 5,
river: 'severn',
length: 47
},
{
date: 2,
depth: 3,
river: 'trent',
length: 63
},
]
console.log(prepareChartData(data, 'date', ['depth', 'length'], ['river']))
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |