I am trying to understand the right usage to achieve my collapsible tree d3 but unable to establish the proper parent/child references since I cannot use "parent". Attempting to use parentID.
This is my dataset I am testing with:
var result = [
{ "id": 1, "name": "Top Level", "parent": null, "parentId": "" },
{ "id": 2, "name": "PROD", "parent": "Top Level", "parentId": 1 },
{ "id": 3, "name": "QAT", "parent": "Top Level", "parentId": 1 },
{ "id": 4, "name": "App1", "parent": "PROD", "parentId": 2 },
{ "id": 5, "name": "App1", "parent": "QAT", "parentId": 3 },
{ "id": 6, "name": "ServerPROD001", "parent": "App1", "parentId": 4 },
{ "id": 7, "name": "ServerQAT001", "parent": "App1", "parentId": 5 }
];
and based on the collapsible tree:
// convert the flat data into a hierarchy
var treeData = d3.stratify()
.id(function (d) { return d.name; })
.parentId(function (d) { return d.parent })
(result);
This works fine if I do not include items 6 and 7. If I do include these I get an ambiguity error which makes sense because it cannot determine which "App1" to associate to.
I tried changing the code to use the parentId but just get an error of "missing:1" now.
// convert the flat data into a hierarchy
var treeData = d3.stratify()
.id(function (d) { return d.name; })
.parentId(function (d) { return d.parentId })
(result);
Note - I cannot change the "App1" name values to something unique as they will exist in multiple areas with that given name.
Since you have the id that is unique and not the name:
// convert the flat data into a hierarchy
var treeData = d3.stratify()
.id(function (d) { return d.id; }) // return the id instead of the name
.parentId(function (d) { return d.parentId })
(result);
and then set the name you need to be displayed like this:
// assign the name to each node as the initial name
treeData.each(function(d) {
d.name = d.data.name;
});
A working example can be found here, based on this
:)
Good luck!
Related
I am using stratify to build a d3 tree from a flat data structure. However, some fields are missing when I try to call them with d.data.fieldname
Here is my data structure:
var flatData = [
{"name": "Data Encrypted", "parent": null, "category": "test", "score": null },
{"name": "Malware on Target", "parent": "Data Encrypted", "category": "test", "score": null },
{"name": "Malware executed", "parent": "Data Encrypted", "category": "test", "score": "1" },
{"name": "Files modified", "parent": "Data Encrypted", "category": "test", "score": "1" },
];
I am building the hierarchical data structure with this stratify command:
var treeData = d3.stratify()
.id(function(d) { return d.name; })
.parentId(function(d) { return d.parent; })
(flatData);
The d3 tree is displayed correctly, and I can expand / collapse nodes etc, and display the ID and Name of each node using d.data.id and d.data.name respectively. If I try and use d.data.score or d.data.category to display data I get an 'undefined' error.
Any information that can help me get past this issue would be greatly appreciated.
I have a very similar task as D3.js nesting and rollup at same time and solution provided by #altocumulus for d3.js v3 works perfectly fine for me (tested). However, I am using d3.js v4 across my website and I am having difficulties replicating the same approach with v4 - I am not getting the same results. Perhaps, because I don't understand the sumChildren function. Please feel free to provide a better or different approach on how to restructure the loaded csv file as json with subtotals at every node level using d3.js v4. In my case, I need to have Population at the Country, State and the City levels.
Disclaimer: I have been using SO for many years and in most of the cases I got my answers from questions posted by other people, but this is my first question. In addition to this I am noob in d3.js
population.csv:
Country,State,City,Population
"USA","California","Los Angeles",18500000
"USA","California","San Diego",1356000
"USA","California","San Francisco",837442
"USA","Texas","Austin",885400
"USA","Texas","Dallas",1258000
"USA","Texas","Houston",2196000
index.html:
<!DOCTYPE html>
<html>
<head>
<title> Test D3.js</title>
</head>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.4.0/d3.min.js"></script>
<body>
<script>
d3.csv("population.csv", function(error, data) {
if (error) throw error;
var nested = d3.nest()
.key(function(d) { return d.Country; })
.key(function(d) { return d.State; })
.rollup(function(cities) {
return cities.map(function(c) {
return {"City": c.City, "Population": +c.Population };
});
})
.entries(data);
// Recursively sum up children's values
function sumChildren(node) {
node.Population = node.values.reduce(function(r, v) {
return r + (v.values ? sumChildren(v) : v.Population);
},0);
return node.Population;
}
// Loop through all top level nodes in nested data,
// i.e. for all countries.
nested.forEach(function(node) {
sumChildren(node);
});
// Output. Nothing of interest below this line.
d3.select("body").append("div")
.style("font-family", "monospace")
.style("white-space", "pre")
.text(JSON.stringify(nested,null,2));
});
</script>
</body>
</html>
Results:
[
{
"key": "USA",
"values": [
{
"key": "California",
"value": [
{
"City": "Los Angeles",
"Population": 18500000
},
{
"City": "San Diego",
"Population": 1356000
},
{
"City": "San Francisco",
"Population": 837442
}
]
},
{
"key": "Texas",
"value": [
{
"City": "Austin",
"Population": 885400
},
{
"City": "Dallas",
"Population": 1258000
},
{
"City": "Houston",
"Population": 2196000
}
]
}
],
"Population": null
}
]
Desired Results:
[
{
"key": "USA",
"values": [
{
"key": "California",
"values": [
{
"City": "Los Angeles",
"Population": 18500000
},
{
"City": "San Diego",
"Population": 1356000
},
{
"City": "San Francisco",
"Population": 837442
}
],
"Population": 20693442
},
{
"key": "Texas",
"values": [
{
"City": "Austin",
"Population": 885400
},
{
"City": "Dallas",
"Population": 1258000
},
{
"City": "Houston",
"Population": 2196000
}
],
"Population": 4339400
}
],
"Population": 25032842
}
]
The v4 changelog tells us that
When used in conjunction with nest.rollup, nest.entries now returns {key, value} objects for the leaf entries, instead of {key, values}.
It is this little renaming from values to value within the leaf nodes which eventually breaks the code. Changing the helper function accordingly should get you back on track:
// Recursively sum up children's values
function sumChildren(node) {
if (node.value) {
node.values = node.value; // Ensure, leaf nodes will also have a values array
delete node.value; // ...instead of a single value
}
node.Population = node.values.reduce(function(r, v) {
return r + (v.value? sumChildren(v) : v.Population);
},0);
return node.Population;
}
Pushing nested data into a Map inside a List
Can anyone tell me:
How do i push a task to either of these users (List items) ideally by specific user id?
Thanks in advance.
My code:
const initialState = Immutable.List([
Immutable.Map({
"id": 1,
"name": "Abe Bell",
"tasks": [
{
"id": 1,
"title": "Get haircut",
"status": false
}
]
}),
Immutable.Map({
"id": 2,
"name": "Chad Dim",
"tasks": [
{
"id": 2,
"title": "Get real job",
"status": false
}
]
})
])
First, the way you're building this structure, the tasks array will not be an immutable instance, I think that is not what you want, you can use Immutable.fromJS to transform all the nested arrays and maps into a Immutable instance.
The way your data is structured you'll have to navigate through the list of users and perform the update when the id matches.
One way of doing that is using map
const initialState = Immutable.fromJS([
{
"id": 1,
"name": "Abe Bell",
"tasks": [
{
"id": 1,
"title": "Get haircut",
"status": false
}
]
},
{
"id": 2,
"name": "Chad Dim",
"tasks": [
{
"id": 2,
"title": "Get real job",
"status": false
}
]
}
]);
let userId = 2;
let newState = initialState.map(user => {
if (user.get('id') !== userId) {
return user;
}
return user.update('tasks', tasks => {
return tasks.push(Immutable.fromJS({
id: 3,
title: "new task",
status: false
}))
});
});
Although this will do what you want,I think you should change your data to a map instead of a list if this kind of operation is something recurrent in your application. This will make things easier and faster.
const initialState = Immutable.fromJS({
"1": {
"id": 1,
"name": "Abe Bell",
"tasks": [
{
"id": 1,
"title": "Get haircut",
"status": false
}
]
},
"2": {
"id": 2,
"name": "Chad Dim",
"tasks": [
{
"id": 2,
"title": "Get real job",
"status": false
}
]
}
});
let userId = "2";
let newState = initialState.updateIn([userId, 'tasks'], tasks => {
return tasks.push(Immutable.fromJS({
id: 3,
title: "new task",
status: false
}));
});
When I select any particular field in the bar graph, all the graphs changes accordingly.
I want to introduce a heading above the graphs which should show which data is currently displayed in the graphs and the count.
So what changes should I make in my code?
var data = [{
"city": "New York",
"neighborhood": "N/A",
"hits": 200
}, {
"city": "New York",
"neighborhood": "Brooklyn",
"hits": 225
}, {
"city": "New York",
"neighborhood": "Queens",
"hits": 1
}, {
"city": "San Francisco",
"neighborhood": "Chinatown",
"hits": 268
}, {
"city": "San Francisco",
"neighborhood": "Downtown",
"hits": 22
}, {
"city": "Seattle",
"neighborhood": "N/A",
"hits": 2
}, {
"city": "Seattle",
"neighborhood": "Freemont",
"hits": 25
}];
var pieChart = dc.pieChart("#pieChart"),
rowChart = dc.rowChart("#rowChart");
var ndx = crossfilter(data),
cityDimension = ndx.dimension(function (d) {
return d.city;
}),
cityGroup = cityDimension.group().reduceSum(function (d) {
return d.hits;
}),
neighborhoodDimension = ndx.dimension(function (d) {
return d.neighborhood;
}),
neighborhoodGroup = neighborhoodDimension.group().reduceSum(function (d) {
return d.hits;
});
pieChart.width(200)
.height(200)
.slicesCap(4)
.dimension(cityDimension)
.group(cityGroup);
pieChart.filter = function() {};
rowChart.width(500)
.height(500)
.dimension(neighborhoodDimension)
.group(neighborhoodGroup);
dc.renderAll();
<div id="pieChart"> </div>
<div id="rowChart"> </div>
For displaying the current filters, you can use spans with class='reset' andclass='filter'` within the charts, as demonstrated in the annotated stock example
For displaying the counts of records filtered, you can use the dataCount widget or the numberDisplay widget.
Anything else, you'll have to implement the display itself, probably hooking the filtered event to determine when the filters have changed, and changing your own div using e.g. d3 or jQuery.
I am trying to create pie charts using d3js. I dont face any issues when i used simple json file. But later when i tried to use slightly more complex json, i am getting these 2 errors.
Error: Problem parsing d="M0,-300A300,300 0 1,1 NaN,NaNLNaN,NaNA20,20 0 1,0 0,-20Z"
Error: Problem parsing d="MNaN,NaNA300,300 0 1,1 NaN,NaNLNaN,NaNA20,20 0 1,0 NaN,NaNZ"
from my understanding the error is at arcs.append("path").attr("d", arc). but donno how to fix it.
here is the d3js code
<div id="sample1">
<script>
var r = 300;
var color = d3.scale.category10()
var canvas = d3.select("#sample1").append("svg")
.attr("width", 700)
.attr("height", 700);
var group = canvas.append("g")
.attr("transform", "translate(350,350)");
var arc = d3.svg.arc()
.innerRadius(20)
.outerRadius(r);
var pie = d3.layout.pie()
.value(function(d) {
d.children.forEach(function(x) {
return x.value;
});
})
.sort(null);
d3.json("js/mydata.json", function(data) {
var arcs = group.selectAll(".arc")
.data(pie(data.children))
.enter()
.append("g")
.attr("class", "arc");
arcs.append("path")
.attr("d", arc)
.attr("fill", function(d) {
d.data.children.forEach(function(x) {
return color(x.name);
});
});
})
</script>
</div>
here is my json
{
"name": "All",
"children": [
{
"name": "Main1",
"children": [
{
"name": "Sub1",
"value": 6.25
},
{
"name": "Sub2",
"value": 12.50
},
{
"name": "Sub3",
"value": 6.25
}
]
},
{
"name": "Main2",
"children": [
{
"name": "Sub4",
"value": 6.25
},
{
"name": "Sub5",
"value": 12.50
},
{
"name": "Sub6",
"value": 6.25
}
]
},
{
"name": "Main3",
"children": [
{
"name": "Sub7",
"value": 6.25
},
{
"name": "Sub8",
"value": 12.50
},
{
"name": "Sub9",
"value": 6.25
}
]
},
{
"name": "Main4",
"children": [
{
"name": "Sub10",
"value": 6.25
},
{
"name": "Sub11",
"value": 12.50
},
{
"name": "Sub12",
"value": 6.25
}
]
}
]
}
Updated:
The approach given by "Lars Kotthoff" worked. how ever this approach does not fix my actual problem which i dint mentioned here. The problem is, i want to give a border to all the Main1,Main2,Main3,Main4. it means although there are 12 colors here borders will be only 4 i.e grouping all three colors into one based on ite parent.
Below picture shows my requirement.