Highcharts .update modifying original data array - javascript
I'm encountering a weird issue that's probably my doing but I've tried everything I can come across.
I have one stacked column chart with eight different data sets. I have a select field with the titles of each data set.
const charts = [
{
"title": "Chart One",
"subtitle": "Subtitle One",
"source": "<p>Source One</p>\n",
"content": "<p>Content One</p>\n",
"series": [
{
"name": "Legend One",
"data": [0,0,0,0,0,0,0,0,0,0,null,0,0,0,0,null,0],
"stack": 0
},
{
"name": "Legend Two",
"data": [1085,1364,2398,2362,3954,6612,6388,8841,8397,6021,null,4962,7407,2825,2143,null,4823],
"stack": 0
}
]
},
{
"title": "Chart Two",
"subtitle": "Subtitle Two",
"source": "<p>Source Two</p>\n",
"content": "<p>Content Two</p>\n",
"series": [
{
"name": "Legend One",
"data": [380,608,469,285,634,1496,712,3059,1821,1049,null,916,2240,612,895,null,1064],
"stack": 0
},
{
"name": "Legend Two",
"data": [705,756,1928,2078,3320,5116,5676,5782,6576,4973,null,4046,5167,2212,1248,null,3759],
"stack": 0
}
]
},
{
"title": "Chart Three",
"subtitle": "Subtitle Three",
"source": "<p>Source Three</p>\n",
"content": "<p>Content Three</p>\n",
"series": [
{
"name": "Legend One",
"data": [648,932,1708,1326,2246,4646,4143,6732,6042,4222,null,3268,5723,1987,1501,null,3322],
"stack": 0
},
{
"name": "Legend Two",
"data": [438,432,690,1037,1708,1966,2244,2110,2355,1799,null,1694,1685,838,642,null,1501],
"stack": 0
}
]
},
{
"title": "Chart Four",
"subtitle": "Subtitle Four",
"source": "<p>Source Four</p>\n",
"content": "<p>Content Four</p>\n",
"series": [
{
"name": "Legend One",
"data": [380,608,469,285,634,1496,712,3118,2498,3567,null,1411,2687,698,1156,null,1479 ],
"stack": 0
},
{
"name": "Legend Two",
"data": [705,756,1928,2078,3320,5116,5676,5724,5899,2455,null,3551,4720,2126,987,null,3344],
"stack": 0
}
]
},
{
"title": "Chart Five",
"subtitle": "Subtitle Five",
"source": "<p>Source Five</p>\n",
"content": "<p>Content Five</p>\n",
"series": [
{
"name": "Legend One",
"data": [561,852,1362,2012,3404,5643,6195,8153,8158,5905,null,4502,6243,2646,1750,null,4305],
"stack": 0
},
{
"name": "Legend Two",
"data": [524,512,1036,350,550,969,193,689,240,117,null,460,1165,178,393,null,518],
"stack": 0
}
]
},
{
"title": "Chart Six",
"subtitle": "Subtitle Six",
"source": "<p>Source Six</p>\n",
"content": "<p>Content Six</p>\n",
"series": [
{
"name": "Legend One",
"data": [120,209,397,655,1115,2584,2944,3322,3075,2266,null,1751,2606,1032,740,null,1704],
"stack": 0
},
{
"name": "Legend Two",
"data": [965,1155,2000,1708,2840,4027,3443,5519,5323,3755,null,3211,4802,1793,1403,null,3118],
"stack": 0
}
]
},
{
"title": "Chart Seven",
"subtitle": "Subtitle Seven",
"source": "<p>Source Seven</p>\n",
"content": "<p>Content Seven</p>\n",
"series": [
{
"name": "Legend One",
"data": [294,494,391,800,1765,4227,5474,7212,7705,5836,null,3755,4640,2070,1463,null,3490],
"stack": 0
},
{
"name": "Legend Two",
"data": [791,870,2007,1563,2189,2385,914,1630,692,186,null,1207,2768,755,680,null,1333],
"stack": 0
}
]
},
{
"title": "Chart Eight",
"subtitle": "Subtitle Eight",
"source": "<p>Source Eight</p>\n",
"content": "<p>Content Eight</p>\n",
"series": [
{
"name": "Legend One",
"data": [486,741,1160,1825,3105,5205,6012,7553,7723,5780,null,4259,5754,2424,1596,null,4035],
"stack": 0
},
{
"name": "Legend Two",
"data": [599,623,1237,537,849,1407,376,1288,674,242,null,703,1654,401,547,null,787],
"stack": 0
}
]
}
]
When a user selects the chart they want to view, I then grab the data needed charts[selectedIndex].series and pass that to:
chartObj.update({
series: charts[selectedIndex].series
}, false );
chartObj.redraw();
This overall works and the chart is updated correctly, however, chart[0].series gets updated with the last selected chart data. The chart array is getting modified for some odd reason. It only modifies the first chart in the array. For the life of me, I can't figure out why this is occurring. Any insight would be greatly appreciated.
I've also tried a for loop going through the chartObj series and matching it to the desired charts array item, and using .setData with the 'data' but that had it's own issues. This did not modify the original array, however the chart would no longer update. I tried the same loop with .update as well.
Codepen with the error: https://codepen.io/rossberenson/pen/mdrWgPe
I dug a little deeper and found that the chart.update() call was switching out the nested series array but wasn't updating the entire chart object, more than that even cloning the series array using map(o => ({...o})) doesn't clone the nested data arrays so they are still prone to mutation. (you could use map(o => ({...o, data: [...o.data]})) but it's fragile if your series/data structure changes).
So... here is a quick snippet that stores the datasets in their own array and sets the Highchart.series option to a separate active object.
The active object is initially assigned a cloned version of the first chart object using JSON for deep cloning – see this question for deeper discussion of its shortcomings and better options What is the most efficient way to deep clone an object in JavaScript?
When the select change event fires, the chart object at the newly selected index is cloned using the same method and assigned to the active object.
Finally, chart.update() is called pointing to the same active.series as when the highchart class was instantiated.
const cloneSample = (sample) => {
// Crude JSON deep clone – see referenced question for discussion
return JSON.parse(JSON.stringify(sample));
}
let active = cloneSample(samples[0]);
const highchart = Highcharts.chart('container', {
chart: { type: 'bar' }, title: { text: active.title },
xAxis: { categories: ['Apples', 'Oranges', 'Pears', 'Grapes', 'Bananas'] },
yAxis: { min: 0, title: { text: 'Total fruit consumption' } }, legend: { reversed: false },
plotOptions: { series: { stacking: 'normal' } },
series: active.series
});
const select = document.querySelector('.select');
select.addEventListener('change', () => {
let
selected = select.value,
newSample = cloneSample(samples[selected]);
active = newSample;
highchart.update({
title: { text: active.title },
series: active.series,
});
});
<script src="https://code.highcharts.com/highcharts.js"></script>
<select class="select">
<option value="0">Sample One</option>
<option value="1">Sample Two</option>
<option value="2">Sample Three</option>
</select>
<div id="container"></div>
<script>
const samples = [
{
"title": "Sample One",
"series": [
{
name: 'John',
data: [5, 3, 4, 7, 2]
}, {
name: 'Jane',
data: [2, 2, 3, 2, 1]
}, {
name: 'Tara',
data: [3, 4, 4, 2, 5]
}
]
},
{
"title": "Sample Two",
"series": [
{
name: 'Donna',
data: [2, 1, 4, 6, 2]
}, {
name: 'Mark',
data: [1, 4, 3, 4, 1]
}, {
name: 'Tim',
data: [6, 4, 9, 3, 5]
}
]
},
{
"title": "Sample Three",
"series": [
{
name: 'Mara',
data: [3, 3, 1, 7, 2]
}, {
name: 'Tom',
data: [0, 1, 2, 4, 1]
}, {
name: 'Lara',
data: [3, 8, 5, 2, 5]
}
]
}
]
</script>
Related
Is it possible to print the label of the object instead the id in material ui DataGrid
So I have two tables in my database. I want a DataGrid UI to print all my columns except the categoryId I want to print a label instead (like in selection valueOptions) : const columns: GridColDef = [ { "field": "title", "headerName": "Titel", "width": 350, "editable": true }, { "field": "categoryId", "headerName": "Kategorie", "width": 250, "editable": true, "type": "singleSelect", "valueOptions": [ { "value": "miv560972ynqzk9", "label": "stuff 1" }, { "value": "1t7n08l9tfdwotn", "label": "stuff 2" }, ] } ] const rows: GridRowsProp = [ { "categoryId": "miv560972ynqzk9", "title": "Lineare Algebra Hausaufga", }, { "categoryId": "1t7n08l9tfdwotn", "title": "Test", }, ] So I want something like this: const rows: GridRowsProp = [ { "categoryId": { value:"miv560972ynqzk9", label: "stuff1"}, "title": "Lineare Algebra Hausaufga", }, { "categoryId": { value: "1t7n08l9tfdwotn", label: "stuff2" }, "title": "Test", }, ]
How can I add the result of a recursive function in a json?
I have this json: [ { "question": "1.1", "level": 1, "id": 4, "answers": [ { "text_answer": "NO", "questions": [ { "question": "1.1.1", "level": 2, "id": 3, "answers": [] } ] }, { "text_answer": null, "questions": [ { "question": "1.1.2", "level": 2, "id": 2, "answers": [ { "text_answer": "SI", "questions": [ { "question": "1.1.2.1", "level": 3, "id": 1, "answers": [] } ] } ] } ] } ] } ] this json is dynamic and can have n amount of levels under the fields answers and questions that is why it is necessary to go through them with a recursive function I want to get this output: [ { "question": "1.1", "level": 1, "id": 4, "children": [ { "question": "1.1.1", "text_answer": "NO", "level": 2, "id": 3, "children": [] }, { "question": "1.1.2", "text_answer": null, "level": 2, "id": 2, "children": [ { "question": "1.1.2.1", "text_answer": "SI", "level": 3, "id": 1, "children": [] } ] } ] } ] the fields answers and questions no longer exist, they are renamed children and the content of both is unified. I think this recursive function should do a double foreach, but I don't know how to do it. function format(d) { if (d.respuestas) { d.respuestas.forEach((d) => { format; }); } } format(data); var data= [ { "question": "1.1", "level": 1, "id": 4, "answers": [ { "text_answer": "NO", "questions": [ { "question": "1.1.1", "level": 2, "id": 3, "answers": [] } ] }, { "text_answer": null, "questions": [ { "question": "1.1.2", "level": 2, "id": 2, "answers": [ { "text_answer": "SI", "questions": [ { "question": "1.1.2.1", "level": 3, "id": 1, "answers": [] } ] } ] } ] } ] } ] /* [ { "question": "1.1", "level": 1, "id": 4, "children": [ { "question": "1.1.1", "text_answer": "NO", "level": 2, "id": 3, "children": [] }, { "question": "1.1.2", "text_answer": null, "level": 2, "id": 2, "children": [ { "question": "1.1.2.1", "text_answer": "SI", "level": 3, "id": 1, "children": [] } ] } ] } ] */ function format(d) { if (d.answers) { d.answers.forEach((d) => { format; }); } } format(data); **note:** I changed the previous structure that I put to the question to make myself understand better.
Here is one approach via recurssion. I've commented the code to explain what exactly is going on line by line. function _process(item) { let result = item // we define a copy of item -> result // this is renaming the field 'answers' (if found) if (item.answers) { result.children = item.answers.map(_process) // process each item separately delete result.answers } // if there is a question field, extract the first item in the array and merge the attributes with result if (item.questions) { result = { ...result, ..._process(item.questions[0]) // also try to process the item before merging it (to check if there are nested 'questions' or 'answers' fields) } delete result.questions // remove the questions field } return result } function process(data) { return data.map(_process) // start the recurssion for top-level objects } // sample data to test out const data = [{ "question": "1.1", "level": 1, "id": 4, "answers": [{ "text_answer": "NO", "questions": [{ "question": "1.1.1", "level": 2, "id": 3, "answers": [] }] }, { "text_answer": null, "questions": [{ "question": "1.1.2", "level": 2, "id": 2, "answers": [{ "text_answer": "SI", "questions": [{ "question": "1.1.2.1", "level": 3, "id": 1, "answers": [] }] }] }] } ] }] const result = process(data) console.log(JSON.stringify(result, null, 2))
You're on the right track in writing a recursive function. Where I think you're going wrong is in thinking about 'pushing' it to an array or object to store the data. Instead, what should happen is that your recursive function returns the formatted data, and then that data is added to new json object which is sent back up the call stack. var data = { "name": "1.question", "answer": "YES", "children": [{ "name": "1.1 question", "answer": "yes" }, { "name": "1.2 question", "answer": "NO", "children": [{ "name": "1.2.1 question", "answer": "NO" }] }, { "name": "1.3 question", "answer": "YES", "children": [{ "name": "1.3.1 question", "answer": "yes", "children": [{ "name": "1.3.1.1 question", "answer": "YES" }] }] } ] } function format(d) { if (d.children) { // if there are children you are going to recurse deeper // use the Array.prototype.map function to _transform_ each of the children. const formattedChildren = d.children.map(v => { return format(v); }); // Notice that I'm returning a new object here, as well as // An aggregation of the already transformed data return { data: "new formatted parent node data goes here", children: formattedChildren }; } else { // _always_ have a check for 'is it a leaf node'. // If it's a leaf node node, just format it. //Notice that I'm returning a new object here return { data: "new formatted leaf node data goes here" }; } } console.log(format(data)); I don't know what you are trying to achieve, so I've left this blank. But this is the template for how you would recursively traverse and transform a nested object like this. (Note that you don't need to have a data key, I've just put that in as a place holder. It looks like you want name and answered keys.
It works for me: // here is your JSON var json = [{ "question": "1.1", "level": 1, "id": 4, "answers": [{ "text_answer": "NO", "questions": [{ "question": "1.1.1", "level": 2, "id": 3, "answers": [] }] }, { "text_answer": null, "questions": [{ "question": "1.1.2", "level": 2, "id": 2, "answers": [{ "text_answer": "SI", "questions": [{ "question": "1.1.2.1", "level": 3, "id": 1, "answers": [] }] }] }] } ] }] // main function change(branch) { // the first level has a bit different structure try { if (branch.level == 1) { return { question: branch.question, level: branch.level, id: branch.id, children: change(branch.answers) } } } catch (e) {} // next levels var new_branch = []; for (var i = 0; i < branch.length; i++) { new_branch.push({ question: branch[i].questions[0].question, text_answer: branch[i].text_answer, level: branch[i].questions[0].level, id: branch[i].questions[0].id, children: [change(branch[i].questions[0].answers)].flat(1) }); } return new_branch; } // output console.log(JSON.stringify(change(json[0])));
Restructuring JSON in JavaScript
I have this JSON which I'm parsing using NodeJS and it needs to be restructured into the second JSON which I've added below. In the first JSON, the rows object has two pages objects (any number of pages objects can be present) which contains all the same keys and values with the exception of values and display keys. { "pages": [ { "label": "SomeLabel", "name": "Some", "sections": [ { "type": "Repeat", "label": "Label 1", "name": "Name 1", "rows": [ { "pages": [ { "label": "Label 1", "name": "Name 1", "sections": [ { "type": "Flow", "label": "Label 2", "name": "Name 2", "answers": [ { "label": "Question Label", "question": "Question", "values": [ "Value A" ], "valuesMetadata": [ { "display": "Display A", "row": { "columns": [] } } ] } ] } ] } ] }, { "pages": [ { "label": "Label 1", "name": "Name 1", "sections": [ { "type": "Flow", "label": "Label 2", "name": "Name 2", "answers": [ { "label": "Question Label", "question": "Question", "values": [ "Value B" ], "valuesMetadata": [ { "display": "Display B", "row": { "columns": [] } } ] } ] } ] } ] } ], "footer": null } ] } ] } In the second JSON the rows object has a single pages object, inside of which the values and display keys have multiple values (the non-common values). { "pages": [ { "label": "SomeLabel", "name": "Some", "sections": [ { "type": "Repeat", "label": "Label 1", "name": "Name 1", "rows": [ { "pages": [ { "label": "Label 1", "name": "Name 1", "sections": [ { "type": "Flow", "label": "Label 2", "name": "Name 2", "answers": [ { "label": "Question Label", "question": "Question", "values": [ "Value A", "Value B" ], "valuesMetadata": [ { "display": [ "Display A", "Display B" ], "row": { "columns": [] } } ] } ] } ] } ] } ], "footer": null } ] } ] } So, I want to know the fast and easy steps to do this. Please let me know the process and methods to solve this. Thanks
If I understand you correctly, you want to combine all pages in a single page that holds all information. This can be achieved using the Array.reduce function. reduce takes an array and combines all elements to a single value using a function (provided by you) to combine the first two elements until only one is left (i.e. 1 * 2 => new1; new1 * 3 => new2 where * represents your function). Your problem would look something like this: rows[0].pages = rows[0].pages.reduce((currentElement, currentState) => { if (!currentState) { // first iteration return first element but make sure display is an array currentElement.sections[0].answers[0].valuesMetadata[0].display = [currentElement.sections[0].answers[0].valuesMetadata[0].display]; return currentElement; } // add values of current element to arrays in current state currentState.sections[0].answers[0].values .concat(currentElement.sections[0].answers[0].values); currentState.sections[0].answers[0].valuesMetadata[0].display .concat(currentElement.sections[0].answers[0].valuesMetadata[0].display); return currentState; }); currentElement is the object of the array that is currently reduced, currentState is the intermediate result of the reduction. PS: The object looks like you are way too many arrays where you would not need them. The given code snippet works only for the first element in each array (hence the [0]s. If you really do have multiple values in each array you would have to iterate over all of those accordingly.
Ramda to loop over array
Loop may be the wrong term, but it kind of describes what I am attempting. I want to give structure to flat data, but I also need to keep track of the array it came from. Basically my rules are (per array): If level 1 exists- give it the name of the item, and a typechild array. EACH time a level 1 appears (even in the same array) it should create a new entry. Inside typechild, put the any items with level >1 If NO level 1 exists- give it the name of the item, and a typechild array. My code below is almost there, with the exception that it should create an array EVERYTIME it sees a level 1. My example will make sense: Input data [ { "title": "Test 1", "type": [{ "name": "Animal", "level": 1 }, { "name": "Food", "level": 1 }, { "name": "Chicken", "level": 3 } ] }, { "title": "Test 2", "type": [{ "name": "Foo", "level": 2 }] } ] Note: Animal and Food are both LEVEL 1 items. So it should create two ARRAYS like so... Desired output [ { name: "Animal", typechild: [ { level: 2, name: "Chicken" } ] }, { name: "Food", typechild: [ { level: 2, name: "Chicken" } ] }, { name: "NoName", typechild: [ { level: 2, name: "Foo" } ] } ] Ramda attempt (try here: https://dpaste.de/JQHw): const levelEq = (n) => pipe(prop('level'), equals(n)); const topLevel = pipe(prop('type'), find(levelEq(1))); const topLevelName = pipe(topLevel, propOr('NoName', 'name')); const extract2ndLevel = pipe(pluck('type'), flatten, filter(levelEq(2))); const convert = pipe( groupBy(topLevelName), map(extract2ndLevel), map(uniq), toPairs, map(zipObj(['name', 'typechild'])) );
Something like this? var output = [{ "name": "Animal", "typechild": [{ "name": "Chicken", "level": 3 }, { "name": "Dog", "level": 2 }] }, { "name": "Food", "typechild": [{ "name": "Chicken", "level": 3 }] }, { "name": "No name", "typechild": [{ "name": "Foo", "level": 2 }, { "name": "Baz", "level": 2 }] }] let out = {}, typechild = {}, k; const data = [{ "title": "Test 1", "type": [{ "name": "Animal", "level": 1 }, { "name": "Food", "level": 1 }, { "name": "Chicken", "level": 3 }] }, { "title": "Test 2", "type": [{ "name": "Foo", "level": 2 }] }, { "title": "Test 3", "type": [{ "name": "Baz", "level": 2 }] }, { "title": "Test 4", "type": [{ "name": "Animal", "level": 1 }, { "name": "Dog", "level": 2 }] }] data.forEach((node) => { k = false; typechild[node.title] = []; node.type && node.type.forEach((t, i) => { if (t.level == 1) { k = true; !out[t.name] ? out[t.name] = { name: t.name, typechild: typechild[node.title] } : out[t.name].typechild = out[t.name].typechild.concat(typechild[node.title]); } else { typechild[node.title].push(t); } if (i == node.type.length - 1 && !k && typechild[node.title].length) { out['No name'] = out['No name'] || { name: 'No name', typechild: [] }; out['No name'].typechild = out['No name'].typechild.concat(typechild[node.title]); } }); }); console.log(JSON.stringify(Object.values(out)));
Check for Parent Child node in JSON using angular JS
I am looking for JSON parsing reference, from where I can jump to question and check for Child question based on Yes section. I didn't find anything related to check for child node check in JSON. Angular is my base framework. some use cases : on load show Root question, On selection show next questions which is child of root then go one jump to number of questions from tree. treeObj={ "Root_Element": { "id": "myTree", "dt": { "choice": { "id": '0', "title": "Which color", "description": "Choose color ?", "choice": [ { "id": 1, "title": "Yellow", "description": "Yellow ? , "choice": [ { "id": 5, "title": "Dark Yellow", "description": "Dark Yellow ? }, { "id": 4, "title": "Light Yellow", "description": "Light Yellow ? } ] }, { "id": 2, "title": "Red", "description": "Red ?" }, { "id": 3, "title": "Green", "description": "Green ? } ] } } } }
If the number of levels in the JSON object is fixed and if it does not grow dynamically, you can use the ES6 destructuring to read the data from the nested JSON. Below is an example var metadata = { title: "Scratchpad", translations: [ { locale: "de", localization_tags: [ ], last_edit: "2014-04-14T08:43:37", url: "/de/docs/Tools/Scratchpad", title: "JavaScript-Umgebung" } ], url: "/en-US/docs/Tools/Scratchpad" }; var { title: englishTitle, translations: [{ title: localeTitle }] } = metadata; console.log(englishTitle); // "Scratchpad" console.log(localeTitle); // "JavaScript-Umgebung"