I have hierarchical data being used to create an svg in my application. I need to find the length of the longest chain in that datasource. I could go through the datasource level by level, recursively going deeper into it to read _children, calling a recursive function to count the levels, but I am sure there must be a better way. This is further complicates because the datasource can go both directions from the node it starts on with _children and _parents.
var findChildren = function (ds, level) {
if (!ds._children || ds._children.length == 0) {
return level;
}
var longest = level + 1;
ds._children.forEach(function (item) {
var result = findChildren(item, level + 1);
if (result > longest) {
longest = result;
}
});
return longest;
}
This is the function I am currently using, with an identical one that checks ds._parents to go the other way, passing the result of one as the starting level of the other. I am just sure there must be a better way...
For example, the same data could be in three ways, depending where the user opened the tree from.
{"number":1,"type":"Delivery","_parents":[{"number":1,"type":"Order","_parents":[{"number":1,"type":"Quote"}]}]}
{"number":1,"type":"Order","_parents":[{"number":1,"type":"Quote"}],
"_children":[{"number":1,"type":"Delivery"}]}
{"number":1,"type":"Quote","_children":[{"number":1,"type":"Order","_children":[{"number":1,"type":"Delivery"}]}]}
You said that you...
need to find the length of the longest chain in that datasource.
That is the length going to the root to the deepest leaf in the data structure. There are convenient D3 methods to quickly find the deepest leaf.
So, suppose we have a hierarchical data like this:
{
"name": "Eve",
"children": [{
"name": "Cain"
}, {
"name": "Seth",
"children": [{
"name": "Enos"
}, {
"name": "Noam"
}]
}, {
"name": "Abel"
}, {
"name": "Awan",
"children": [{
"name": "Enoch"
}]
}, {
"name": "Azura"
}]
}
When you pass it to d3.hierarchy()...
var hierarchy = d3.hierarchy(data);
... it automatically creates a property named depth in each node:
node.depth - zero for the root node, and increasing by one for each descendant generation.
So, we just need a simple function to get the biggest depth value. For instance:
var longest = d3.max(hierarchy.descendants().map(function(d) {
return d.depth
}));
Here is a demo:
var data = {
"name": "Eve",
"children": [{
"name": "Cain"
}, {
"name": "Seth",
"children": [{
"name": "Enos"
}, {
"name": "Noam"
}]
}, {
"name": "Abel"
}, {
"name": "Awan",
"children": [{
"name": "Enoch"
}]
}, {
"name": "Azura"
}]
};
var hierarchy = d3.hierarchy(data);
var longest = d3.max(hierarchy.descendants().map(function(d) {
return d.depth
}));
console.log("The longest chain has " + (longest + 1) + " levels.")
<script src="https://d3js.org/d3.v4.min.js"></script>
Related
I'm trying to create a piece of JavaScript that can read through specific parts of a linked object and place them iteratively into another piece of code which then places the code into HTML and into the front-end.
I've managed to get the fetch part working whereby it pulls in the JSON and can be read in the console, when summoned. Once the code runs, I'm able to refer to the data and bring out the whole dataset with something like:
console.log(AllOffers);
and I can drill down into something like the offerName in the JSON by using the following syntax in a variable and calling it in the console:
var OfferName = data.offersBreakdown.allOffers[0].offers[0].offerName;
However this only pulls in the first iteration of offerName because in the variable I've set it to look into the first iteration of its parent, 'offers'. What I'm looking to do is create a variable which prints all of the offerName data so that I can call on it instead of the data_test variable further down in the code, which processes the data into HTML. Sounds confusing? It is.
Ideally what I think I need is to be able to ask it to look into each child item of 'offers' (rather than just the first one) and then have it look for 'offerName'. I can't work out how one would achieve this. The best I can come up with is to remove the [0] from 'offers', but if I do that, it returns undefined as the result.
Here's my JavaScript (and a bit of jQuery):
<script>
// fetch call for the JSON data (see below)
fetch('api_url', {
headers: {
'Authorization': 'auth_token'
}
})
.then(response => response.json())
.then(function (data) {
var AllOffers = data.offersBreakdown.allOffers[0];
var AllOffers_Offers = data.offersBreakdown.allOffers[0].offers;
var OfferName = data.offersBreakdown.allOffers[0].offers[0].offerName;
var OfferImageUrl = data.offersBreakdown.allOffers[0].offers[0].imageUrl;
console.log(AllOffers);
function createCard(cardData) {
var cardTemplate = [
'<div class="card">',
'<p>My name is: ',
cardData.Offer || 'No offer',
'</p>',
'<p>My job is: ',
cardData.Img || 'No image',
'</p></div>'
];
// a jQuery node
return jQuery(cardTemplate.join(''));
}
var data_test = [
{ "Name": OfferName, "Img": OfferImageUrl },
{ "Name": OfferName, "Img": OfferImageUrl },
{ "Name": OfferName, "Img": OfferImageUrl },
];
var cards = jQuery();
// Store all the card nodes
data_test.forEach(function(item, i) {
cards = cards.add(createCard(item));
});
// Add them to the page... for instance the <body>
jQuery(function() {
jQuery('body').append(cards);
});
</script>
Here's the JSON
<script>
// the JSON
{
"offersBreakdown": {
"totalAddedOffers": 0,
"totalOffers": 2,
"totalAddedRewards": 0,
"totalRewards": 0,
"totalAddedStreakOffers": 0,
"totalStreakOffers": 0,
"allOffers": [
{
"offers": [
{
"offerName": "Offer name 1",
"imageUrl": "https://url_path_1.jpg"
},
{
"offerName": "Offer name 2",
"imageUrl": "https://url_path_2.jpg"
},
{
"offerName": "Offer name 3",
"imageUrl": "https://url_path_3.jpg"
},
{
"offerName": "Offer name 4",
"imageUrl": "https://url_path_4.jpg"
}
]
}
</script>
I'm assuming what you're looking for is a way to loop through all of the offerNames, in which case a simple for loop would suffice. Since your data includes nested arrays and objects, we need two loops, one to iterate through your allOffers array and then a nested for loops to iterate through the offers array inside of your allOffers array
var data = {
"offersBreakdown": {
"totalAddedOffers": 0,
"totalOffers": 2,
"totalAddedRewards": 0,
"totalRewards": 0,
"totalAddedStreakOffers": 0,
"totalStreakOffers": 0,
"allOffers": [{
"offers": [{
"offerName": "Offer name 1",
"imageUrl": "https://url_path_1.jpg"
}, {
"offerName": "Offer name 2",
"imageUrl": "https://url_path_2.jpg"
}, {
"offerName": "Offer name 3",
"imageUrl": "https://url_path_3.jpg"
}, {
"offerName": "Offer name 4",
"imageUrl": "https://url_path_4.jpg"
}]
}]
}
};
var allOffers = [];
var jsonObjectAllOffers = data.offersBreakdown.allOffers;
for (var i = 0; i < jsonObjectAllOffers.length; i++) {
var offers = jsonObjectAllOffers[i].offers;
for (var j = 0; j < offers.length; j++) {
var objectToAppend = {
"Name": offers[j]["offerName"],
"Img": offers[j]["imageUrl"]
};
allOffers.push(objectToAppend);
}
}
console.log(allOffers);
And now you can use your allOffers variable to loop through with the "forEach" and make into HTML
This is the sample json:
{
"search": {
"facets": {
"author": [
],
"language": [
{
"value": "nep",
"count": 3
},
{
"value": "urd",
"count": 1
}
],
"source": [
{
"value": "West Bengal State Council of Vocational Education & Training",
"count": 175
}
],
"type": [
{
"value": "text",
"count": 175
}
],
}
}
There are several ways to delete key search.facets.source:
delete search.facets.source
delete jsobObj['search']['facets']['source']
var jsonKey = 'source';
JSON.parse(angular.toJson(jsonObj), function (key, value) {
if (key != jsonKey)
return value;
});
Above 1 & 2 are not dynamic, and 3 is one of the way but not a proper way. Because if source is present in another node then it will not work. Please anybody can tell me how to delete it dynamically in any kind of nested key. Because we can not generate sequence of array dynamically in above 2.
Assuming you're starting from this:
let path = 'search.facets.source';
Then the logic is simple: find the search.facets object, then delete obj['source'] on it.
Step one, divide the path into the initial path and trailing property name:
let keys = path.split('.');
let prop = keys.pop();
Find the facets object in your object:
let parent = keys.reduce((obj, key) => obj[key], jsonObj);
Delete the property:
delete parent[prop];
I have found out another solution, it is very easy.
var jsonKey = 'search.facets.source';
eval('delete jsonObj.' + jsonKey + ';');
I am working on a d3 visualization to create a sunburst. I am also adding a functionality to search for a particular arc so that the sunburst displays arcs with only the searched label. If the initial object is:
{
"name": "root",
"children": [
{
"name": "A",
"color": "red",
"children": [
{
"name": "B",
"color": "red"
}
]
},
{
"name": "C",
"color": "red",
"children": [
{
"name": "D",
"color": "red"
}
]
},
{
"name": "E",
"color": "red",
"children": [
{
"name": "B",
"color": "red"
}
]
}
]
}
I would like to filter this such that it returns the whole hierarchy which contains the searched name anywhere in the hierarchy. Below is a sample output required when searched for "name":"B"
{
"name": "root",
"children": [
{
"name": "A",
"color": "red",
"children": [
{
"name": "B",
"color": "red"
}
]
},
{
"name": "E",
"color": "red",
"children": [
{
"name": "B",
"color": "red"
}
]
}
]
}
Please tell me if anything else might be needed. Thank you.
Code snippet of how I tried to filter.
var path = svg.selectAll("path")
.data(partition.nodes(root))
.enter()
.append("path")
.filter(function(d){
return (d.name=="B");
})
.attr("d", arc)
.style("fill", function(d) {
console.log(d.name);
return color((d.children ? d : d.parent).name);
})
This returns only "name":"B" arc and not the hierarchy.
If you want the .filter() way to work for you, you will have to flatten your JSON data apply a filter and then unflatten it. You can check out flattening and unflattening if you are interested.
I personally prefer the depth-first-search (or bfs also works) and pruning where required approach. Lets assume your json data is present in mydata javascript var:
d3.json("jsonData.json",function(error,jsonData){
if(error)
return console.warn(error);
var mydata = jsonData;
dfs(mydata,'B');
});
Now your dfs() function will look something like this:
function dfs(data,label){
if(!('children' in data))
return data.name==label;
for(var i=0;i<data.children.length;++i)
if(dfs(data.children[i],label)==flase)
data.children.splice(i--,1);
return data.children.length > 0 || data.name==label;
}
What is happening here is that we are doing a depth-first search of the JSON data. The first IF condition checks if we are at the leaf node level and returns false if the 'name' property is anything but 'B'. Next we traverse all children of the current node, call dfs() for each child and prune that child if it returns false. At the end we return false if all children of current node have been pruned and if the 'name' property of current node is not 'B' (the condition that the non-leaf node also has to be pruned from the hierarchy). Hope this helps. Let me know if I missed something.
My hunch is that you want to color the searching node and its parent in sunburst.
To do this i have made a function which recursively colors a node's parent like this:
function colorGraph(node){
var sel = svg.selectAll("path").filter(function (d) {
return (node==d);
});
sel.style("opacity", 1);
//if parent present change its opacity
if(node.parent){
colorGraph(node.parent)
}
}
Full working code here
You will need to pre-filter, rather than doing it within the D3 pipeline.
Define a filter function we will call hasName which checks if a particular object has the desired name, or, recursively, if any of its children have it:
// Does the object contain a specified name, however many levels down?
function hasName(obj, name) {
return function _hasName(o) {
return o.name === name || (o.children || []).some(_hasName);
}(obj);
}
Now filter:
data.children = data.children.filter(function(child) { return hasName(child, 'B'); });
I need help pushing the values from a filtered json, I need this generate a nested ul list, I can not modify the json format at this point, I you check the console.log you will see the values to create the list, at this point I can't figure how to complete the 'for loop' to render the html markup needed, any help will be appreciated, this is the jsfiddle http://jsfiddle.net/43jh9hzz/, and if you check the console log you will see the values.
This is the Js:
var json='';
var property_set = new Set();
function iterate(obj, stack) {
json="<ul>";
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
iterate(obj[property], stack + '.' + property);
}
else {
// console.log(property);
property_set.add(property);
json+="<li>";
if(typeof obj[property] !== "number") {
json+="<li>"+obj[property]+"</li>";
console.log(obj[property]);
}
}
} json += "</li>";
}
}
var listEl = document.getElementById('output');
iterate(jsonObj)
And this is the json format:
var jsonObj =
{
"level_1": [
{
"level_1_name": "CiscoSingaporeEBC",
"level_2": [
{
"level_2_name": "Khoo Tech Puat",
"level_2_id": 2222,
"level_3": [
{
"name": "Boon Leong Ong",
"id": 6919
},
{
"name": "Kiat Ho",
"id": 6917
},
{
"name": "Overall Experience",
"id": 6918
}
]
}
]
},
{
"level_1_name": "CiscoLondonEBC",
"level_2": [
{
"level_2_name": "Bernard Mathews Ltd.",
"level_2_id": 2367,
"level_3": [
{
"name": "Barry Pascolutti",
"id": 7193
},
{
"name": "Kathrine Eilersten",
"id": 7194
},
{
"name": "Martin Rowley",
"id": 7189
}
]
},
{
"level_2_name": "FNHW Day 1",
"level_2_id": 5678,
"level_3": [
{
"name": "Jurgen Gosch",
"id": 7834
},
{
"name": "Overall Experience",
"id": 7835
}
]
},
{
"level_2_name": "Groupe Steria Day 1",
"level_2_id": 2789,
"level_3": [
{
"name": "Adam Philpott",
"id": 7919
},
{
"name": "Pranav Kumar",
"id": 7921
},
{
"name": "Steve Simlo",
"id": 7928
}
]
}
]
}
]
};
enter code here
I'm not sure if I am interpretting your request correctly, but I think this is what you want: http://jsfiddle.net/mooreinteractive/43jh9hzz/1/
Basically, you are calling the iterate function to run, but then that's it. The function actually needs to also return the value it generates.
I've added to the end of the function, after the for loop completes:
return json;
Do now the function returns the value it generated, but there are some other issues too. When you recursively call the iterate function again inside the iterate function, you actually want to add what it returns to the current json string housing all of your returned value.
So on that line I changed it from:
iterate(obj[property], stack + '.' + property);
to
json += iterate(obj[property], stack + '.' + property);
Now that other value will come back as well inside the main list you were creating in the first run of the function. Ok so that's pretty close, but one more small thing. I think when you added additional surrounding LI, you actually wanted to do an UL. I changed those to ULs and now I think the result is like a UL/LI list representing the text parts of the JSON object.
Again, that may not be exactly what you were after, but I think the main take away is using the function to return the value, not just generate it, then do nothing with it.
I have an Array with this kind of values:
val = [ ['L-2-4-1','john','bla1'],
['L-1-1-26','bohn','bla2'],
['L-2-1','cohn','bla3'],
['L-1-1-05','rohn','bla4'],
['L-1-1','gohn','bla5']
['L-2-3-1','zohn','bla-finally'] ];
The number-sequence is always unique and "0" is never used.
What I'm trying to get would be something like this:
ser = [ [undefined],
[ [undefined],[ ['gohn'],['bla5'] ], [undefined], ... , [ ['bohn'], ['blah2'] ] ],
...
];
The purpose is to be able to access the data like this:
ser[2][4][1][0]; // Array('john','bla1')
ser[1][1][0]; // Array('gohn','bla5')
ser[1][1][26][0]; // Array('bohn','bla2')
and also to loop through all elements.. for instance:
for(var i = 0; i <= ser[1][1].length; i++){ //code }
The main problem I have is that I was not able to set the variables the same way I intend to read them. Because this does NOT work, since I need to declare all arrays separately as arrays (right?)
var ser[1][1][26][0] = ['john','bla1']; // Nop;
I don't know the maximum depth of the tree
Trying to build the arrays from "inside out" or from "right to left" -however it is best described- I always end up overwriting previously set array elements.
Maybe the whole idea is too complicated (or at least not ideal) for the purpose? What would you suggest? I have the feeling I´m trying to do the right thing but the wrong way... Something like organizing marbles on a glass surface. Everything keeps moving around...
Have you considered representing your data in JSON?
It allows for complex structures that are otherwise too confusing to keep in your head. It's like XML meets JavaScript arrays. Rather self-describing and easy to follow. You can read the lengths and sizes of objects easily and it's quite fast. You can use values instead of array positions and re-think the structure of your data.
http://json.org/example.html
Here is a record in JSON:
{
"id": "0001",
"type": "donut",
"name": "Cake",
"ppu": 0.55,
"batters":
{
"batter":
[
{ "id": "1001", "type": "Regular" },
{ "id": "1002", "type": "Chocolate" },
{ "id": "1003", "type": "Blueberry" },
{ "id": "1004", "type": "Devil's Food" }
]
},
"topping":
[
{ "id": "5001", "type": "None" },
{ "id": "5002", "type": "Glazed" },
{ "id": "5005", "type": "Sugar" },
{ "id": "5007", "type": "Powdered Sugar" },
{ "id": "5006", "type": "Chocolate with Sprinkles" },
{ "id": "5003", "type": "Chocolate" },
{ "id": "5004", "type": "Maple" }
]
},
http://labs.adobe.com/technologies/spry/samples/data_region/JSONDataSetSample.html
short and sweet:
var i, j, t, final = [];
for (i = 0; i < val.length; i++) {
t = val[i][0].split('-');
for (j = 1; j < 5; j++) {
t[j] = parseInt(t[j], 10) || 0;
}
final[t[1]] = final[t[1]] || [];
final[t[1]][t[2]] = final[t[1]][t[2]] || [];
final[t[1]][t[2]][t[3]] = final[t[1]][t[2]][t[3]] || [];
final[t[1]][t[2]][t[3]][t[4]] = final[t[1]][t[2]][t[3]][t[4]] || [];
final[t[1]][t[2]][t[3]][t[4]].push(val[i].slice(1));
}
final now has the correct data as you specified...
however, you might want to consider using objects instead of arrays (change all [] to {}) as the random insertion points in arrays lead to series of empty (null) values, the only caveat would be that you'd have to use a for (var key in obj) style loop...
hope this helps -ck
IF YOU NEED DYNAMIC DEPTH
var i, j, t, o, depth = 4, final = [];
for (i = 0; i < val.length; i++) {
t = val[i][0].split('-');
o = final;
for (j = 1; j <= depth; j++) {
t[j] = parseInt(t[j], 10) || 0;
o[t[j]] = o[t[j]] || [];
o = o[t[j]];
}
o.push(val[i].slice(1));
}
now depth is the constant at which the data is stored missing or unparsable "keys" or "indices" depending on how you want to think of them, default to 0
enjoy -ck