d3 hierarchy ability to average children node values - javascript

I have a d3 visualization that has a JSON object similar to the following where I would like to average up the value of score on the lowest nodes and dynamically add that average to the parent node above...and so on. It doesn't look like d3 has an easy method to do this. What I'd like is to have the final JSON output look like the second example.
{
"name": "A1",
"children": [
{
"name": "B1",
"children": [
{
"name": "B1-C1",
"children": [
{
"name": "B1-C1-D1",
"children": [
{
"name": "B1-C1-D1-E1",
"value": 30,
"score": 0.8
},
{
"name": "B1-C1-D1-E2",
"value": 35,
"score": 0.5
}
]
},
{
"name": "B1-C1-D2",
"children": [
{
"name": "B1-C1-D2-E1",
"value": 31,
"score": 0.4
},
{
"name": "B1-C1-D2-E2",
"value": 23,
"score": 0.7
}
]
}
]
}
]
}
]
}
What I'd like the final JSON object to look like:
{
"name": "A1",
"scoreAvg": 0.625,
"children": [
{
"name": "B1",
"scoreAvg": 0.625,
"children": [
{
"name": "B1-C1",
"scoreAvg": 0.625,
"children": [
{
"name": "B1-C1-D1",
"scoreAvg": 0.7,
"children": [
{
"name": "B1-C1-D1-E1",
"value": 30,
"score": 0.8
},
{
"name": "B1-C1-D1-E2",
"value": 35,
"score": 0.6
}
]
},
{
"name": "B1-C1-D2",
"scoreAvg": 0.55,
"children": [
{
"name": "B1-C1-D2-E1",
"value": 31,
"score": 0.4
},
{
"name": "B1-C1-D2-E2",
"value": 23,
"score": 0.7
}
]
}
]
}
]
}
]
}

You can use a recursive function:
const obj = {
"name": "A1",
"children": [{
"name": "B1",
"children": [{
"name": "B1-C1",
"children": [{
"name": "B1-C1-D1",
"children": [{
"name": "B1-C1-D1-E1",
"value": 30,
"score": 0.8
},
{
"name": "B1-C1-D1-E2",
"value": 35,
"score": 0.5
}
]
},
{
"name": "B1-C1-D2",
"children": [{
"name": "B1-C1-D2-E1",
"value": 31,
"score": 0.4
},
{
"name": "B1-C1-D2-E2",
"value": 23,
"score": 0.7
}
]
}
]
}]
}]
}
function getWithAverageScore(objToRecurse) {
// If I have a score I am already done
if (objToRecurse.score) {
return objToRecurse;
}
// Otherwise, I get my children with their average score
const children = objToRecurse.children.map(getWithAverageScore);
return {
...objToRecurse,
children,
// And I set my scoreAvg to their average (score or scoreAvg)
scoreAvg: children.reduce((total, { score, scoreAvg }) => total + (score || scoreAvg), 0) / children.length
};
}
console.log(JSON.stringify(getWithAverageScore(obj), null, 2))

let o = {
"name": "A1",
"children": [
{
"name": "B1",
"children": [
{
"name": "B1-C1",
"children": [
{
"name": "B1-C1-D1",
"children": [
{
"name": "B1-C1-D1-E1",
"value": 30,
"score": 0.8
},
{
"name": "B1-C1-D1-E2",
"value": 35,
"score": 0.5
}
]
},
{
"name": "B1-C1-D2",
"children": [
{
"name": "B1-C1-D2-E1",
"value": 31,
"score": 0.4
},
{
"name": "B1-C1-D2-E2",
"value": 23,
"score": 0.7
}
]
}
]
}
]
}
]
};
function avgUp(object){
object.avgScore = 0;
if(object.children){
for(child of object.children){
object.avgScore += avgUp(child);
}
object.avgScore = object.avgScore /Math.max(1,object.children.length);
return object.avgScore;
}else{
return object.score;
}
}
avgUp(o);
console.log(JSON.stringify(o));

Related

Transformation of nested object

I am new to JavaScript and Node JS
want to transform the following nested object with student
Data:
[{
"id": 1,
"name": "A",
"children": [{
"id": 2,
"name": "B",
"children": [{
"id": 3,
"name": "C"
},
{
"id": 4,
"name": "D"
}
]
}]
}]
to
Expected:
[{
"student": {
"id": 1,
"name": "A"
},
"children": [{
"student": {
"id": 2,
"name": "B"
},
"children": [{
"student": {
"id": 3,
"name": "C"
}
},
{
"student": {
"id": 4,
"name": "D"
}
}
]
}]
}]
I guess you are seeking a solution for an array with multiple student objects. So you can use the map method to modify them.
const original = [{
"id": 1,
"name": "A",
"children": [{
"id": 2,
"name": "B",
"children": [{
"id": 3,
"name": "C"
},
{
"id": 4,
"name": "D"
}
]
}]
}]
const modified = original.map(stu => {
return {
student: {
id: stu.id,
name: stu.name,
},
children: stu.children
}
})

How do I flatten a (forest of) trees?

I have a forest of trees of arbitrary height, more or less like this:
let data = [
{ "id": 2, "name": "AAA", "parent_id": null, "short_name": "A" },
{
"id": 10, "name": "BBB", "parent_id": null, "short_name": "B", "children": [
{
"id": 3, "name": "CCC", "parent_id": 10, "short_name": "C", "children": [
{ "id": 6, "name": "DDD", "parent_id": 3, "short_name": "D" },
{ "id": 5, "name": "EEE", "parent_id": 3, "short_name": "E" }
]
},
{
"id": 4, "name": "FFF", "parent_id": 10, "short_name": "F", "children": [
{ "id": 7, "name": "GGG", "parent_id": 4, "short_name": "G" },
{ "id": 8, "name": "HHH", "parent_id": 4, "short_name": "H" }
]
}]
}
];
And I'm trying to produce a representation of all the root-to-leaves paths, something like this
[
[
{
"id": 2,
"name": "AAA"
}
],
[
{
"id": 10,
"name": "B"
},
{
"id": 3,
"name": "C"
},
{
"id": 6,
"name": "DDD"
}
],
[
{
"id": 10,
"name": "B"
},
{
"id": 3,
"name": "C"
},
{
"id": 5,
"name": "EEE"
}
],
[
{
"id": 10,
"name": "B"
},
{
"id": 4,
"name": "F"
},
{
"id": 7,
"name": "GGG"
}
],
[
{
"id": 10,
"name": "B"
},
{
"id": 4,
"name": "F"
},
{
"id": 8,
"name": "HHH"
}
]
]
So I wrote the following code:
function flattenTree(node, path = []) {
if (node.children) {
return node.children.map(child => flattenTree(child, [...path, child]));
} else {
let prefix = path.slice(0, path.length - 1).map(n => ({ id: n.id, name: n.short_name }));
let last = path[path.length - 1];
return [...prefix, { id: last.id, name: last.name } ];
}
}
let paths = data.map(n => flattenTree(n, [n]));
but paths comes out with extra nesting, like this:
[
[
{
"id": 2,
"name": "AAA"
}
],
[
[
[
{
"id": 10,
"name": "B"
},
{
"id": 3,
"name": "C"
},
{
"id": 6,
"name": "DDD"
}
],
[
{
"id": 10,
"name": "B"
},
{
"id": 3,
"name": "C"
},
{
"id": 5,
"name": "EEE"
}
]
],
[
[
{
"id": 10,
"name": "B"
},
{
"id": 4,
"name": "F"
},
{
"id": 7,
"name": "GGG"
}
],
[
{
"id": 10,
"name": "B"
},
{
"id": 4,
"name": "F"
},
{
"id": 8,
"name": "HHH"
}
]
]
]
]
I lost count of the many ways in which I tried to fix this, but it does look like the algorithm should not produce the extra nesting -- or my eyes are just so crossed by now that I couldn't see my mistake if someone stuck their finger on it.
Can someone help? Feel free to peruse this JSFiddle https://jsfiddle.net/png7x9bh/66/
The extra nestings are created by map. map just wraps the results into an array and returns them, it doesn't care if it is called on child nodes or not. Use reduce and just concat (or push, whatever suits your performance) the results into the first level array directly:
let data = [{"id":2,"name":"AAA","parent_id":null,"short_name":"A"},{"id":10,"name":"BBB","parent_id":null,"short_name":"B","children":[{"id":3,"name":"CCC","parent_id":10,"short_name":"C","children":[{"id":6,"name":"DDD","parent_id":3,"short_name":"D"},{"id":5,"name":"EEE","parent_id":3,"short_name":"E"}]},{"id":4,"name":"FFF","parent_id":10,"short_name":"F","children":[{"id":7,"name":"GGG","parent_id":4,"short_name":"G"},{"id":8,"name":"HHH","parent_id":4,"short_name":"H"}]}]}];
function flattenTree(node, path = []) {
let pathCopy = Array.from(path);
pathCopy.push({id: node.id, name: node.name});
if(node.children) {
return node.children.reduce((acc, child) => acc.concat(flattenTree(child, pathCopy)), []);
}
return [pathCopy];
}
let result = data.reduce((result, node) => result.concat(flattenTree(node)), []);
console.log(JSON.stringify(result, null, 3));

Using angularjs forEach loops

I am getting this type of json in my $scope of angularjs:
$scope.someStuff = {
"id": 2,
"service": "bike",
"min": "22",
"per": "100",
"tax": "1",
"categoryservices": [
{
"id": 32,
"category": {
"id": 1,
"name": "software"
}
},
{
"id": 33,
"category": {
"id": 2,
"name": "hardware"
}
},
{
"id": 34,
"category": {
"id": 3,
"name": "waterwash"
}
}
]
}
I want to use angularjs forEach loop and i want to get only category name,
My expected output:
[{"name":"software"}, {"name":"hardware"}, {"name":"waterwash"}]
You can use Array.map()
The map() method creates a new array with the results of calling a provided function on every element in the calling array.
$scope.someStuff.categoryservices.map((x) => { return { name: x.category.name}})
var obj = {
"id": 2,
"service": "bike",
"min": "22",
"per": "100",
"tax": "1",
"categoryservices": [{
"id": 32,
"category": {
"id": 1,
"name": "software"
}
},
{
"id": 33,
"category": {
"id": 2,
"name": "hardware"
}
},
{
"id": 34,
"category": {
"id": 3,
"name": "waterwash"
}
}
]
};
console.log(obj.categoryservices.map((x) => {
return {
name: x.category.name
}
}))
You can use map method by passing a callback function as parameter.
const someStuff = { "id": 2, "service": "bike", "min": "22", "per": "100", "tax": "1", "categoryservices": [ { "id": 32, "category": { "id": 1, "name": "software" } }, { "id": 33, "category": { "id": 2, "name": "hardware" } }, { "id": 34, "category": { "id": 3, "name": "waterwash" } } ] }
let array = someStuff.categoryservices.map(function({category}){
return {'name' : category.name}
});
console.log(array);

How to collect value in javascript?

I have an array like this in Javascript. Something like this
[
{
"id": 1,
"facilities": [
{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
}
]
},
{
"id": 2,
"facilities": [
{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
},
{
"id": 13,
"name": "Snack",
"label": "Snack"
}
]
},
{
"id": 3,
"facilities": [
{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
},
{
"id": 14,
"name": "Petrol",
"label": "Petrol"
}
]
}
]
I want to collect data and grouping data facilities of the array in Javascript, something like this.
"facilities": [
{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
},
{
"id": 13,
"name": "Snack",
"label": "Snack"
},
{
"id": 14,
"name": "Petrol",
"label": "Petrol"
}
]
So, basically, group by facilities. I just don't know how to handle the grouping of similar facilities values.
Assuming the facility ids are unique:
const facilities = input.reduce((memo, entry) => {
entry.facilities.forEach((f) => {
if (!memo.some((m) => m.id === f.id)) {
memo.push(f)
}
})
return memo
}, [])
You can iterate through all rows and collect (id, entity) map.
Index map allows us to not search already collected entities every time.
Then you can convert it to an array with object keys mapping.
let input = [
{"id": 1, "facilities": [{"id": 10, "name": "Wifi", "label": "Wifi"}, {"id": 12, "name": "Toll", "label": "Toll"} ] },
{"id": 2, "facilities": [{"id": 10, "name": "Wifi", "label": "Wifi"}, {"id": 12, "name": "Toll", "label": "Toll"}, {"id": 13, "name": "Snack", "label": "Snack"} ] },
{"id": 3, "facilities": [{"id": 10, "name": "Wifi", "label": "Wifi"}, {"id": 12, "name": "Toll", "label": "Toll"}, {"id": 14, "name": "Petrol", "label": "Petrol"} ] }
];
let index = input.reduce((res, row) => {
row.facilities.forEach(f => res[f.id] = f);
return res;
}, {});
let result = Object.keys(index).map(id => index[id]);
console.log({facilities: result});
You could use a Set for flagging inserted objects with the given id.
var data = [{ id: 1, facilities: [{ id: 10, name: "Wifi", label: "Wifi" }, { id: 12, name: "Toll", label: "Toll" }] }, { id: 2, facilities: [{ id: 10, name: "Wifi", label: "Wifi" }, { id: 12, name: "Toll", label: "Toll" }, { id: 13, name: "Snack", label: "Snack" }] }, { id: 3, facilities: [{ id: 10, name: "Wifi", label: "Wifi" }, { id: 12, name: "Toll", label: "Toll" }, { id: 14, name: "Petrol", label: "Petrol" }] }],
grouped = data.reduce(
(s => (r, a) => (a.facilities.forEach(b => !s.has(b.id) && s.add(b.id) && r.push(b)), r))(new Set),
[]
);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The solution using Array.prototype.reduce() and Set object:
var data = [{"id": 1,"facilities": [{"id": 10,"name": "Wifi","label": "Wifi"},{"id": 12,"name": "Toll","label": "Toll"}]},{"id": 2,"facilities": [{"id": 10,"name": "Wifi","label": "Wifi"},{"id": 12,"name": "Toll","label": "Toll"},{"id": 13,"name": "Snack","label": "Snack"}]},{"id": 3,"facilities": [{"id": 10,"name": "Wifi","label": "Wifi"},{"id": 12,"name": "Toll","label": "Toll"},{"id": 14,"name": "Petrol","label": "Petrol"}]}
];
var ids = new Set(),
result = data.reduce(function (r, o) {
o.facilities.forEach(function(v) { // iterating through nested `facilities`
if (!ids.has(v.id)) r.facilities.push(v);
ids.add(v.id); // saving only items with unique `id`
});
return r;
}, {facilities: []});
console.log(result);
const input = [
{
"id": 1,
"facilities": [
{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
}
]
},
{
"id": 2,
"facilities": [
{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
},
{
"id": 13,
"name": "Snack",
"label": "Snack"
}
]
},
{
"id": 3,
"facilities": [
{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
},
{
"id": 14,
"name": "Petrol",
"label": "Petrol"
}
]
}
]
const result = []
const idx = []
for (const item of input) {
for (const facilityItem of item.facilities) {
if (!idx.includes(facilityItem.id)) {
idx.push(facilityItem.id)
result.push(facilityItem)
}
}
}
console.log(result)
A very simple and easily understood approach.
const data = [{
"id": 1,
"facilities": [{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
}
]
},
{
"id": 2,
"facilities": [{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
},
{
"id": 13,
"name": "Snack",
"label": "Snack"
}
]
},
{
"id": 2,
"facilities": [{
"id": 10,
"name": "Wifi",
"label": "Wifi"
},
{
"id": 12,
"name": "Toll",
"label": "Toll"
},
{
"id": 14,
"name": "Petrol",
"label": "Petrol"
}
]
}
];
let o = {};
let result = [];
data.forEach((d) => {
d.facilities.forEach((f) => {
o[f.id] = f;
});
});
for (let r in o) {
result.push(o[r]);
}
console.log(result);

Create Nested JSON object Javascript

Hello I want to create a JSON Object for storage resources in a post request in java script I have an input array value disk sizes for example below:
request1
input = [10, 20, 30]
"storageResources": [
{
"stats": [
{
"name": "diskSize",
"units": "GB",
"value": 10
},
{
"name": "diskIopsConsumed",
"value": 0
},
{
"name": "diskConsumedFactor",
"value": 1
}
]
},
{
"stats": [
{
"name": "diskSize",
"units": "GB",
"value": 20
},
{
"name": "diskIopsConsumed",
"value": 1
},
{
"name": "diskConsumedFactor",
"value": "NaN"
}
]
},
{
"stats": [
{
"name": "diskSize",
"units": "GB",
"value": 30
},
{
"name": "diskIopsConsumed",
"value": 0
},
{
"name": "diskConsumedFactor",
"value": 1
}
]
},
],
request2:
input [10,20]
"storageResources": [
{
"stats": [
{
"name": "diskSize",
"units": "GB",
"value": 10
},
{
"name": "diskIopsConsumed",
"value": 0
},
{
"name": "diskConsumedFactor",
"value": 1
}
]
},
{
"stats": [
{
"name": "diskSize",
"units": "GB",
"value": 20
},
{
"name": "diskIopsConsumed",
"value": 1
},
{
"name": "diskConsumedFactor",
"value": "NaN"
}
]
}
],
Is the best way to do this with a function or can you send it by properties?
Use Array.prototype.map to return a modified response
$$text.oninput = evt => {
let json = JSON.parse($$text.value)
let result = json.storageResources.map(resource =>
resource.stats.find(e => e.name == 'diskSize').value
)
console.log(result)
}
$$text.oninput()
<textarea id="$$text">{"storageResources":[{"stats":[{"name":"diskSize","units":"GB","value":10},{"name":"diskIopsConsumed","value":0},{"name":"diskConsumedFactor","value":1}]},{"stats":[{"name":"diskSize","units":"GB","value":20},{"name":"diskIopsConsumed","value":1},{"name":"diskConsumedFactor","value":"NaN"}]},{"stats":[{"name":"diskSize","units":"GB","value":30},{"name":"diskIopsConsumed","value":0},{"name":"diskConsumedFactor","value":1}]}]}</textarea>

Categories

Resources