I have the following object:
var series = [{
name: 'Series 1',
data: [1400, 999, 450],
tag: "Tag 1"
}, {
name: 'Series 2',
data: [355, 188, 99],
tag: "Tag 1"
}, {
name: 'Series 3',
data: [205, 488, 104],
tag: "Tag 2"
}];
What I'm trying to do is to perform a groupBy function to that array so for example, calling orderBy("tag") should return the following output:
[{
name: "Series 1 / Series 2",
data: [1755, 1187, 549], // => Sum: Series1[0] + Series2[0], Series1[1] + Series2[1], etc...
tag: "Tag 1"
}, {
name "Series 3",
data: [205, 488, 104],
tag: "Tag 2"
}]
At the moment this is what I've so far:
var seriesArray = [{
name: 'Series 1',
data: [1400, 999, 450],
tag: "Tag 1"
}, {
name: 'Series 2',
data: [355, 188, 99],
tag: "Tag 1"
}, {
name: 'Series 3',
data: [205, 488, 104],
tag: "Tag 2"
}];
const groupBy = (key) => seriesArray.reduce((total, currentValue) => {
const newTotal = total;
if (
total.length &&
total[total.length - 1][key] === currentValue[key]
)
newTotal[total.length - 1] = {
...total[total.length - 1],
...currentValue,
data: parseInt(total[total.length - 1].data[0]) + parseInt(currentValue.data[0]),
};
else newTotal[total.length] = currentValue;
return newTotal;
}, []);
console.log(groupBy('tag'));
As you can see it seems to work the orderBy("tag") but I'm doing the sum operation explicit, the main problem with this is that data[] could have X elements.
Currently your approach assumes that the last item that was added to your total array has the same key that you're trying to group on. This is all good if the data is sorted by key (ie: all "Tag 1"s come first), but can be an issue if its not. I would suggest that instead, you first build an object/Map that is keyed by tag. That way you can use the object to find your accumulated "Tag 1", "Tag 2", etc... objects. Once you've reduced it to an object/Map, you can grab the values (which are the accumulated objects) and put them into an array. To handle the different data array lengths, you can use .map() on your accumulated data array (ie: seen.data) and then add each number with the corresponding number from the current object's data array using the index argument i:
const seriesArray = [{ name: 'Series 1', data: [1400, 999, 450], tag: "Tag 1" }, { name: 'Series 2', data: [355, 188, 99], tag: "Tag 1" }, { name: 'Series 3', data: [205, 488, 104], tag: "Tag 2" }];
const groupBy = (arr, key) => Array.from(arr.reduce((map, currentObj) => {
const groupOnKey = currentObj[key];
const seen = map.get(groupOnKey);
return map.set(groupOnKey, seen ? {
...seen,
name: seen.name + " / " + currentObj.name,
data: seen.data.map((num, i) => num + currentObj.data[i], 0)
} : currentObj);
}, new Map()).values());
console.log(groupBy(seriesArray, 'tag'));
Good challenge, this occurs to me with findIndex
const groupBy = (key) => seriesArray.reduce((total, currentValue) => {
index = total.findIndex(x => x[key] === currentValue[key])
if (index >= 0) {
total[index].data = total[index].data.map(function (num, idx) {
return num + parseInt(currentValue.data[idx]);
});
total[index].name += " / " + currentValue.name;
} else {
total.push(currentValue);
}
return total;
}, []);
I think the logic in your reduce callback function is not going to give you the result you want. I'll explain what that function is doing.
What you want to do
var seriesArray = [{
name: 'Series 1',
data: [1400, 999, 450],
tag: "Tag 1"
},
{
name: 'Series 3',
data: [1400, 999, 450],
tag: "Tag 2"
}, {
name: 'Series 2',
data: [355, 188, 99],
tag: "Tag 1"
},
];
const isMatchingArray = (array1, array2) => {
if (array1.length !== array2.length) {
return false;
}
for (let i = 0; i < array1.length; i++) {
if (array1[i] !== array2[i]) {
return false;
}
}
return true;
}
const groupBy = (key, array) => array.reduce((total, currentValue) => {
const newTotal = total;
if (total.length) {
const matchingItemIndex = total.findIndex(element => {
if (typeof element[key] === 'string') {
return element[key] === currentValue[key];
} else if (Array.isArray(element[key])) {
return isMatchingArray(element[key], currentValue[key]);
}
});
if (matchingItemIndex !== -1) {
newTotal[matchingItemIndex] = {
...total[matchingItemIndex],
...currentValue,
name: total[matchingItemIndex].name + ' / ' + currentValue.name,
data: currentValue.data.map((dataItem, index) => parseInt(dataItem + total[matchingItemIndex].data[index])),
};
} else {
newTotal.push(currentValue)
}
} else {
newTotal.push(currentValue)
};
return newTotal;
}, []);
console.log(groupBy('tag', seriesArray))
console.log(groupBy('data', seriesArray));