d3.js how to properly massage my data for tree layout? - javascript

I'm having a hard time finding the right way to properly structure my json data to achieve what I need with d3.js.
My content is made of "articles", which have "tags" in a many-to-many relationship.
Therefore, an article can have several tags, a tag can have several articles.
I want to represent my content as a node tree, in such way:
But right now, tags and posts are reproduced several times, as such:
How can I avoid the tag & posts duplication, and instead have lines pointing to the correct nodes? Or is there a better way to format my json data to achieve this sort of visualisation?
You can see my code in action at this fiddle.
Here is the data:
var json_data= {
"name": "Histoire du Web",
"children": [
{
"name": "américain",
"type": "tag",
"count": "1",
"children": [
{
"name": "jeff atwood",
"type": "article",
"count": 7
}
]
},
{
"name": "american",
"type": "tag",
"count": "2",
"children": [
{
"name": "Doug Englebart",
"type": "article",
"count": 5
},
{
"name": "jeff atwood",
"type": "article",
"count": 7
}
]
},
{
"name": "coding horror",
"type": "tag",
"count": "1",
"children": []
},
{
"name": "développeur",
"type": "tag",
"count": "1",
"children": [
{
"name": "jeff atwood",
"type": "article",
"count": 7
}
]
},
{
"name": "Engelbart's Law",
"type": "tag",
"count": "1",
"children": []
},
{
"name": "entrepreneur",
"type": "tag",
"count": "1",
"children": [
{
"name": "jeff atwood",
"type": "article",
"count": 7
}
]
},
{
"name": "forum",
"type": "tag",
"count": "1",
"children": [
{
"name": "jeff atwood",
"type": "article",
"count": 7
}
]
},
{
"name": "hypertext",
"type": "tag",
"count": "1",
"children": [
{
"name": "Doug Englebart",
"type": "article",
"count": 5
}
]
},
{
"name": "interaction",
"type": "tag",
"count": "1",
"children": [
{
"name": "Doug Englebart",
"type": "article",
"count": 5
}
]
},
{
"name": "mouse",
"type": "tag",
"count": "1",
"children": [
{
"name": "Doug Englebart",
"type": "article",
"count": 5
}
]
},
{
"name": "stackoverflow",
"type": "tag",
"count": "1",
"children": [
{
"name": "jeff atwood",
"type": "article",
"count": 7
}
]
}
]
};

It is not easy to use a tree layout to represent data where nodes share parents, however it is possible and a detailed description can be found here: https://gist.github.com/GerHobbelt/3683278
From the link:
Of course, some brutal hacking, e.g. duplication of partial trees to convert multi-parent to many times the same with single parent, can be applied, but it might be a much better option to use a true graph layout mechanism, such as d3.layout.force, and apply the proper constraints to make it do what you want.

Related

Extracting all nested IDs

I have below JSON for example where I need to extract all categories Id from the last subcategory if its not empty. For instance: On below JSON I need Ids like mens-apparel-ricky inside last categories object but if this categories was empty I like to extract the last ID like womens-new-arrivals. Basically it should be the list of all these ID's extracted.
Thanks in Advance
"categories": [{
"id": "featured",
"name": "NEW ARRIVALS",
"categories": [{
"id": "featured-new-arrivals",
"name": "New Arrivals",
"categories": [{
"id": "mens-new-arrivals",
"name": "Mens",
"categories": []
}, {
"id": "womens-new-arrivals",
"name": "Womens",
"categories": [{
"id": "mens-apparel-ricky",
"name": "Relaxed",
"categories": []
}, {
"id": "new-arrivals-kids",
"name": "Kids",
"categories": []
}, {
"id": "new-arrivals-accessories",
"name": "Accessories",
"categories": []
}]
}, {
"id": "collections",
"name": "Featured",
"categories": [{
"id": "spring-shop",
"name": "Spring Shop",
"categories": [{
"id": "spring-shop-mens",
"name": "Spring Shop Men's",
"categories": []
}, {
"id": "spring-shop-womens",
"name": "Spring Shop Women's",
"categories": []
}]
}, {
"id": "the-festival-shop",
"name": "The Festival Shop",
"categories": [{
"id": "mens-festival-shop",
"name": "Mens Festival Shop",
"categories": []
}, {
"id": "womens-festival-shop",
"name": "Womens Festival Shop",
"categories": []
}]
}, {
"id": "buddha-collections",
"name": "The Icons Shop",
"categories": [{
"id": "buddha-collections-men",
"name": "Mens",
"categories": []
}, {
"id": "buddha-collections-women",
"name": "Womens",
"categories": []
}]
}, {
"id": "all-black",
"name": "All Black Everything",
"categories": []
}]
}]
}, {
"id": "denim",
"name": "DENIM",
"categories": [{
"id": "denim-view-all",
"name": "Shop All Denim",
"categories": [{
"id": "denim-view-all-mens",
"name": "Mens Denim",
"categories": []
}, {
"id": "denim-view-all-womens",
"name": "Womens Denim",
"categories": []
}, {
"id": "denim-view-all-kids",
"name": "Kids Denim",
"categories": []
}]
}, {
"id": "denim-collections",
"name": "Featured",
"categories": [{
"id": "denim-collections-signature-stitch",
"name": "Big T & Super T Stitch",
"categories": []
}, {
"id": "denim-collections-lighten-up",
"name": "Light Wash Denim",
"categories": []
}, {
"id": "dark-wash-denim",
"name": "Dark Wash Denim",
"categories": []
}]
}]
}]
}
This is what I have tried but being new to coding getting difficult to get list form this complex structure.
var rootCategories = (jsonData.categories);
for (var i=0; i < rootCategories.length; i++)
{
var firstChild = (jsonData.categories[i].categories);
for (var j=0; j < firstChild.length; j++)
{
if ((firstChild[j].categories[j].length != 0)) {
catId = firstChild[j].categories[j].id
console.log(catId);
}
}
}
Below is one possible way to achieve the target.
Code Snippet
// recursive method to get "id"s
const getIds = arr => (
arr.flatMap( // iterate using ".flatMap()" to avoid nesting
({id, categories}) => { // de-structure to directly access "id" & "categories"
if ( // if "categories" is not empty, recurse to next level
Array.isArray(categories) &&
categories.length > 0
) {
return getIds(categories);
} else { // if it is empty, simply return the id
return id;
}
}
)
);
const rawData = {
"categories": [{
"id": "featured",
"name": "NEW ARRIVALS",
"categories": [{
"id": "featured-new-arrivals",
"name": "New Arrivals",
"categories": [{
"id": "mens-new-arrivals",
"name": "Mens",
"categories": []
}, {
"id": "womens-new-arrivals",
"name": "Womens",
"categories": [{
"id": "mens-apparel-ricky",
"name": "Relaxed",
"categories": []
}, {
"id": "new-arrivals-kids",
"name": "Kids",
"categories": []
}, {
"id": "new-arrivals-accessories",
"name": "Accessories",
"categories": []
}]
}, {
"id": "collections",
"name": "Featured",
"categories": [{
"id": "spring-shop",
"name": "Spring Shop",
"categories": [{
"id": "spring-shop-mens",
"name": "Spring Shop Men's",
"categories": []
}, {
"id": "spring-shop-womens",
"name": "Spring Shop Women's",
"categories": []
}]
}, {
"id": "the-festival-shop",
"name": "The Festival Shop",
"categories": [{
"id": "mens-festival-shop",
"name": "Mens Festival Shop",
"categories": []
}, {
"id": "womens-festival-shop",
"name": "Womens Festival Shop",
"categories": []
}]
}, {
"id": "buddha-collections",
"name": "The Icons Shop",
"categories": [{
"id": "buddha-collections-men",
"name": "Mens",
"categories": []
}, {
"id": "buddha-collections-women",
"name": "Womens",
"categories": []
}]
}, {
"id": "all-black",
"name": "All Black Everything",
"categories": []
}]
}]
}, {
"id": "denim",
"name": "DENIM",
"categories": [{
"id": "denim-view-all",
"name": "Shop All Denim",
"categories": [{
"id": "denim-view-all-mens",
"name": "Mens Denim",
"categories": []
}, {
"id": "denim-view-all-womens",
"name": "Womens Denim",
"categories": []
}, {
"id": "denim-view-all-kids",
"name": "Kids Denim",
"categories": []
}]
}, {
"id": "denim-collections",
"name": "Featured",
"categories": [{
"id": "denim-collections-signature-stitch",
"name": "Big T & Super T Stitch",
"categories": []
}, {
"id": "denim-collections-lighten-up",
"name": "Light Wash Denim",
"categories": []
}, {
"id": "dark-wash-denim",
"name": "Dark Wash Denim",
"categories": []
}]
}]
}]
}]
};
console.log(getIds(rawData.categories));
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added in the snippet above.
If you're not afraid of reference loops (shouldn't technically happen in a serialized REST response) you can try recurring function as the simplest way to achieve this
function getIDs(node, isRoot = false) {
if (!node.categories.length) // if I understood your question correctly, you don't want parents to be printed out, otherwise just check for isRoot instead
return isRoot || console.log(node.id);
// with above you won't access non-existent id of the root collection
// because it will stop checking after first true statement
for (const category of categories)
getIDs(category);
}
getIDs(collection, true);

How can i sort array with objects which have property with another array of objects but not with ES6 [duplicate]

This question already has answers here:
Sort nested array of object in javascript
(4 answers)
Closed 3 years ago.
I have this kind of array with objects. In each object except properties i have another array with objects called "secondary_fields". I need to access the last element in the secondary_fields array (secondary_fields[3]) where i have value of when it is created and after that to sort all of the objects
by the date time based on the value inside for example "2020-01-13 17:42:51";
But not with ES6 because i am using old platform where the ES6 version of Java Script is not supported
let arr =
[
{
"external": false,
"link": "--",
"direct": false,
"display_field": "new best article",
"id": "kb_knowledge:33",
"secondary_fields": [
{
"display_value": "Test Admin",
"name": "author",
"label": "Author",
"type": "reference",
"value": "xx"
},
{
"display_value": "25",
"name": "sys_view_count",
"label": "View count",
"type": "integer",
"value": "25"
},
{
"display_value": "2020-01-13 09:44:54",
"name": "sys_updated_on",
"label": "Updated",
"type": "glide_date_time",
"value": "2020-01-13 17:44:54"
},
{
"display_value": "2020-01-13 09:42:51",
"name": "sys_created_on",
"label": "Created",
"type": "glide_date_time",
"value": "2020-01-13 17:42:51"
}
],
},
{
"external": false,
"link": "--",
"direct": false,
"display_field": "How to connect the iPod to Wi-Fi",
"id": "kb_knowledge:11",
"secondary_fields": [
{
"display_value": "John",
"name": "author",
"label": "Author",
"type": "reference",
"value": "xxnnx"
},
{
"display_value": "16",
"name": "sys_view_count",
"label": "View count",
"type": "integer",
"value": "16"
},
{
"display_value": "2019-12-18 08:18:08",
"name": "sys_updated_on",
"label": "Updated",
"type": "glide_date_time",
"value": "2019-12-18 16:18:08"
},
{
"display_value": "2019-10-21 12:27:22",
"name": "sys_created_on",
"label": "Created",
"type": "glide_date_time",
"value": "2019-10-21 19:27:22"
}
],
}
]
You can make use of the array method's sort method and sort on display_value.
arr.sort(function(a, b) {
return new Date(a.secondary_fields[3].display_value) - new Date(b.secondary_fields[3].display_value)
})
Edit
Or I guess in your case,
return new Date(b.secondary_fields[3].display_value) - new Date(a.secondary_fields[3].display_value)
let arr =
[
{
"external": false,
"link": "--",
"direct": false,
"display_field": "new best article",
"id": "kb_knowledge:33",
"secondary_fields": [
{
"display_value": "Test Admin",
"name": "author",
"label": "Author",
"type": "reference",
"value": "xx"
},
{
"display_value": "25",
"name": "sys_view_count",
"label": "View count",
"type": "integer",
"value": "25"
},
{
"display_value": "2020-01-13 09:44:54",
"name": "sys_updated_on",
"label": "Updated",
"type": "glide_date_time",
"value": "2020-01-13 17:44:54"
},
{
"display_value": "2020-01-13 09:42:51",
"name": "sys_created_on",
"label": "Created",
"type": "glide_date_time",
"value": "2020-01-13 17:42:51"
}
],
},
{
"external": false,
"link": "--",
"direct": false,
"display_field": "How to connect the iPod to Wi-Fi",
"id": "kb_knowledge:11",
"secondary_fields": [
{
"display_value": "John",
"name": "author",
"label": "Author",
"type": "reference",
"value": "xxnnx"
},
{
"display_value": "16",
"name": "sys_view_count",
"label": "View count",
"type": "integer",
"value": "16"
},
{
"display_value": "2019-12-18 08:18:08",
"name": "sys_updated_on",
"label": "Updated",
"type": "glide_date_time",
"value": "2019-12-18 16:18:08"
},
{
"display_value": "2019-10-21 12:27:22",
"name": "sys_created_on",
"label": "Created",
"type": "glide_date_time",
"value": "2019-10-21 19:27:22"
}
],
}
]
arr.sort(function(a, b){
return new Date(b.secondary_fields[3].display_value) - new Date(a.secondary_fields[3].display_value)
})
console.log(arr)

Convert nested JSON to flat JSON - In order to insert them as records in SQL

I have a JSON string in nested pattern like below:
NESTED (Just Example):
{
"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" }
]
}
I want an option or way to convert this nested JSON string to a flat JSON string so that I can insert them like a record in SQL table.
Can you please help on how to structure the JSON from nested to flat?

Nested Loops in angularjs

I have been using nested loops to access data of the json object to display the id and type of topping, however its not working. Here's my code:
var j_obj = {
"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"
}]
}
var Outer_log=[];
debugger
angular.forEach(j_obj, function(an_object){
//Outer_log.push("ID : "+an_object.id+" type : "+an_object.type);
angular.forEach(an_object.topping,function(innerobject){
Outer_log.push("ID : "+innerobject.id+" type : "+innerobject.type);
},Outer_log);
});
console.log(Outer_log);
Could someone please highlight the error in above code, Thanks
Without using nested loop you can iterate using angular.forEach like this
var finalArray=[];
angular.forEach(j_obj[0].topping, function(eachobject){
finalArray.push("ID : "+ eachobject.id+" type : "+ eachobject.type);
});
Angulars forEach is intended to iterate over arrays not object. so if you change your code to something like this
var j_obj = [{ ...}] //object is wrapped inside array.
it will work. Another thing is you don't need a nested loop in this case. You can just do:
angular.forEach(j_obj.topping, function(key, value){ ... });
you are iterating over object where as loop run over array.
hope this helps JSfiddle link
var j_obj = [{
"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"
}]
}]
var Outer_log = [];
angular.forEach(j_obj, function(an_object) {
//Outer_log.push("ID : "+an_object.id+" type : "+an_object.type);
angular.forEach(an_object.topping, function(innerobject) {
Outer_log.push("ID : " + innerobject.id + " type : " + innerobject.type);
}, Outer_log);
});
console.log(Outer_log);

Dynamic Hierarchical Router in JavaScript Framework Router

We had settled on using Aurelia as a JS framework, but hit a wall with routing. We have hierarchical navigation, which Aurelia Router does not support (https://github.com/aurelia/router/issues/90).
Navigation also depends on who is logged in, and each path from the primary navigation can have 1, 2 or 3 levels. Here is a sample of what we are doing now using knockoutjs (we are moving to a SPA): http://jsfiddle.net/22dy9yqw/4/. The data source for the navigation has to be a nested JSON object that represents the navigational structure, and the UI has to remain as is (bottom level is a dropdown). Here is an example of our structure:
[
{
"name": "No SubNav",
"href": "\/no-subnav",
"id": "no-subnav"
},
{
"name": "Settings",
"href": "\/settings\/",
"id": "settings",
"children": [
{
"name": "Setting1",
"href": "\/settings\/setting1\/",
"id": "setting1"
}, {
"name": "Setting2",
"href": "\/settings\/setting2\/",
"id": "setting2"
}
]
},
{
"name": "Reports",
"href": "\/reports\/",
"id": "reports",
"children": [
{
"name": "Section1",
"href": "\/reports\/section1\/",
"id": "section1",
"children": [
{
"name": "Section1 Report1",
"href": "\/reports\/section1\/report1",
"id": "report1-1",
}, {
"name": "Section1 Report2",
"href": "\/reports\/section1\/report2",
"id": "report1-2",
}, {
"name": "Section1 Report3",
"href": "\/reports\/section1\/report3",
"id": "report1-3",
}
]
}, {
"name": "Section2",
"href": "\/reports\/section2\/",
"id": "section2",
"children": [
{
"name": "Section2 Report1",
"href": "\/reports\/section2\/report1",
"id": "section2-1",
}, {
"name": "Section2 Report2",
"href": "\/reports\/section2\/report2",
"id": "section2-2",
}, {
"name": "Section2 Report3",
"href": "\/reports\/section2\/report3",
"id": "section2-3",
}, {
"name": "Section2 Report4",
"href": "\/reports\/section2\/report4",
"id": "section2-4",
}, {
"name": "Section2 Report5",
"href": "\/reports\/section2\/report5",
"id": "section2-5",
}
]
}
]
}
]
Can something like this be done easily with Aurelia while still taking advantage of other Router functionality (e.g., mapping)? Is there a better solution? I am new to the JS framework world and am a bit overwhelmed.

Categories