I have some tree-ish data that I stratify using d3.stratify, which gives me what I assume is a proper data structure (it has children, data and parents). However, when I pass it through d3.tree(), the x and y values are all NaNs, which makes plotting them impossible. Based on examples I've seen, I don't need to run d3.hierarchy() since stratify does that for me, and I've tried using hierarchy() without success anyway.
I tried doing JSON.stringify(data) to paste the data here, but it gave me an error about converting circular structures to JSON not being possible, so that didn't work either. What I do know is that each layer (except for the top one) has a children array, a data object and a parent object, so it all seems to be right.
edit: I tried some test data (from this link) and I still get NaNs for x and y. I copied the treeData, put it through d3.hierarchy and then put it through this tree() call and x and y are still NaN.
var treeMap = d3.tree()
.size(width, height2); // 1020, 170 in my case.
var treeData =
{
"name": "Top Level",
"children": [
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{ "name": "Level 2: B" }
]
};
treeData = d3.hierarchy(treeData, function(d) { return d.children; });
var nodes = treeMap(treeData); // gives NaN x and y values.
Ahh, so simple yet so silly. Instead of passing ([height, width]) I was passing (height, width) to the tree function, so of course it failed wildly. Giving it the proper array fixes everything.
Related
I am new to JavaScript and not sure how to write this into a loop. How do I change the node width in the D3 library for Sankey diagrams so that it is not one constant value and instead updates based on the node names?
For example, I have this data:
"nodes": [
{"name":"A" },
{"name":"B" },
{"name":"C" }
],
And I want the node width of A=10, B=20, C=30.
Here is the relevant code from https://bl.ocks.org/tomshanley.
var sankey = d3.sankeyCircular()
.nodeWidth(10)
.nodePadding(40) //note that this will be overridden by nodePaddingRatio
.nodePaddingRatio(0.15)
.size([width, height])
.nodeId(function (d) {
return d.name;
})
.nodeAlign(d3.sankeyJustify)
.iterations(42)
.circularLinkGap(3);
Thank you!
To respect the data-driven approach it would be better to have the width inside your data array:
"nodes": [ {"name":"A", "width": 10 }, ...]
Then it will become:
.nodeWidth(d => d.width)
Otherwise you will need a way to map the name to the width, e.g. with:
var nameToWidth = {"A": 10, ...};
Then it will become:
.nodeWidth(d => nameToWidth[d.name])
When I create the first set of sectors in my pie chart, it works great. Especially, the attribute d of each path is set correctly.
var grx = chart.selectAll(".sector").data(pie(dataPoints))
.enter().append("g").attr("class", "sector");
grx.append("path").attr("d", arc)
.style("fill", function(d) { return colorsFull(d.value); });
Then, I try to use the same pattern to add a new, inner pie chart. The elements are created, which I can confirm by investigating the DOM structure. However, they're not displayed and, even though it's not shown in the console of the JsFiddle, I got it to have a bunch of NaN.
var gry = chart.selectAll(".subSector").data(pie(drillData))
.enter().append("g").attr("class", "subSector");
gry.append("path").attr("d", sub)
.style("fill", function(d) {return colorsDrill(d.value); });
So, I've got an invalid data for the path but I can't see how it goes wrong. Suggestions?
The fiddle is here.
Your drillData is not in the correct format to pass to your pie function. It looks like this:
[
{
"key": "Category 1",
"val": [{ "Category": "", "Key": "", Value: 1}, ... ]
}
...
]
Your pie function, though, is expecting an array of objects with a property val that's a number, not an array (this is what the .value does).
I'm guessing you need to subset your drillData to the one you clicked on something like:
var subData = null;
drillData.forEach(function(d,i){
if (d.key === key) subData = drillData[i].val;
});
You now have in subData an array of objects with a property Value that's a number. We are getting closer but now we need to redefine our pie function since it's expecting val, not Value:
pie.value(function(d){
return d.Value;
});
So, now we can finally call:
pie(subData)
without error. But, we still got a problem, now you are trying to build an inner donut chart with 300+ wedges (it ain't going to be pretty).
Here's an updated fiddle where I started fixing things.
I have created a basic flow chart but I am not getting that how to create next level of flow chart.
I am attaching the image and the jsfiddle link.
Fiddle
here is the data
"process":[
{
"ProcessName" : "nivprocess.exe",
"Username" : "Joh Doe",
"Created" : "10:00:00",
"processDescription" : null,
"process_parent" : null,
"operation" : [
{
"operationDescription":"OP1",
"operationTime":"10:15:15",
"response":"Fail"
},{
"operationDescription":"OP2",
"operationTime":"10:20:00",
"response":"Success"
}
],
},
{
"ProcessName" : "Process2.exe",
"Username" : "Some One",
"Created" : "11:00:00",
"processDescription" : null,
"process_parent" : "process1",
"operation" : [
{
"operationDescription":"OP1",
"operationTime":"10:15:15",
"response":"Fail"
},{
"operationDescription":"OP2",
"operationTime":"10:20:00",
"response":"Success"
}],
},
{
"ProcessName" : "Process3.exe",
"Username" : "Nika Boka",
"Created" : "12:00:00",
"processDescription" : null,
"process_parent" : "process2",
"operation" : [
{
"operationDescription":"OP1",
"operationTime":"10:15:15",
"response":"Fail"
},{
"operationDescription":"OP2",
"operationTime":"10:20:00",
"response":"Success"
}],
}
]}
You're drawing this manually (I assumed the flow chart meant a chart meaning a d3 layout), your data array has 3 data points so this will map to 3 drawn objects directly. I can see your code (which you should attach to the questions too) is drawing lines with two rects (under label text) and four pieces of text for each data point, however it's not processing any of the operation arrays in the data point.
An aside: I'm noticing some clipping, in JS fiddle it helped me to temporarily set the svg tag with a width:
<svg style="padding-top: 100px; width:100%" class="chart">
There's two approaches forward I might try:
Create an empty group associated with each process rect, var ops = chart.selectAll("g g"); then figure out the right way to get a reference to the data point bound to each parent g, lets refer to it by dp. Then do ops.selectAll("g").data(dp.operation).enter().append("g"); On each enter draw that first line to the fork. Then work with each operation within this groups' groups' group to draw the two operation lines, the operation circle and labels similar to work you did before. Notice I'm fuzzy on getting the reference to dp. It might be something like: bar.each(function(dp,i,t){d3.selectAll(t).data(dp.operation).enter().etc;});
Possibly manually setup the right selections. Assuming you made that empty second g like in the "append test", manual setup would look a bit like: data.forEach(function(dp, idx, arr){bar[idx].data(dp.operation).enter().etc;}) where I'm hoping the bar selectAll's indexes are in the same order as the data's. They will match in quantity.
While trying to go with #1, I ended up getting this to get part of the way to where you wanted. It certainly isn't pretty, but you get 1 line to a group for each process, then in each group 1 circle and 1 line for each operation (you'll have to add lines, arrows and labels, and it's a bit weird how I get the offsets):
//appending test
var ops = bar.append("g");
ops
.attr("transform", function(d, i) {
return "translate("+width+",0)";
});
ops
.append('line')
.attr("x1","0")
.attr("y1",lineHeight/2)
.attr("x2",width/8)
.attr("y2",lineHeight/2)
//.attr("transform","translate(0,-"+(lineHeight*.85)+")")
.attr("stroke","#FF0000")
.attr("stroke-width","4");
ops.each(
function(d,i,t){
if ('object'===typeof this) {
var op = d3.select(this).selectAll('g').data(d.operation).enter().append("g");
var xc=1;
op.append("circle")
.attr("cx",lineHeight)
.attr("cy",function(d){return lineHeight/2*xc++-lineHeight/4})
.attr("r",width/4)
.attr("fill", "#ff0000");
var xl1s=1;
var xl1e=1;
op.append("line")
.attr("x1",width/8)
.attr("y1",function(d){return lineHeight/2-width/4-lineHeight/4*xl1s++})
.attr("x2",lineHeight)
.attr("y2",function(d){return lineHeight/2-width/4-lineHeight/4*xl1e++})
.attr("stroke","#FF0000")
.attr("stroke-width","4");
}});
For example, JSON element, where the "categories" field is an array:
{"city": "San Francisco, CA", "business_id": "15", "name": "Parastructure", "date": "2014", "founder_education": "Stanford", "categories": ["Big Data", "Big Data Analytics", "Data Visualization", "Enterprise Software"], "Amount": "500000000"}
Using dc.js, how would I go about selecting each element in the array and adding it to the chart visualization?
var categories = ndx.dimension(function (d) {
return d.categories; // this wouldn't work
});
var categoriesGroup = categories.group();
Crossfilter works best with a flat arrays of records. If you don't like the idea of a reduce function creating maps, as suggested in the comments above, you can create a flat array out of your data basically by "multiplying out" the categories with the records.
So, something like this (using underscores's clone function to duplicate the records):
var product = [];
for(var r in records) {
for(var i = 0; i < r.categories.length; ++i) {
var r2 = _.clone(r);
delete r2.categories;
r2.category = r.categories[i];
product.push(r2);
}
}
(Warning: not tested.) Now, where you used to have n categories in a record, you'll have n records, one for each category. And you can reduce on category okay now.
... Of course it might make your other calculations messy or not work (some hints here), which is why I suggested the other answer first. Unfortunately crossfilter was not designed for dealing with multi-valued fields.
Hi I am currently calling a set of json data using the built D3 request for Json. The current problem I am having is trying to index through my array of data within that request. At the moment I am only able to get the first x & y of my array. It therefore affects my D3 drag as each time I drag a new object it jumps back to the previous position of where the mouse was clicked. Here's a snippet:
d3.json("url/path", function(data) {
var drag = d3.behavior.drag()
.origin(Object)
.on("drag", function(d,i) {
data.locations[0].x += d3.event.dx;
data.locations[0].y += d3.event.dy;
d3.select(this).attr("transform", "translate("+data.locations[0].x+","+data.locations[0].y+")")
});
.attr("transform", function(d,i) {return "translate("+data.locations[0].x+","+data.locations[0].y+")" ;})
});
However if I attempt to index through the whole data of locations e.g.
"translate("+data[i].locations[0].x+","+data[i].locations[0].y+")"
I receive an error "Uncaught TypeError: Cannot read property 'locations' of undefined "
My current json data is structured like so:
{
"section":"a",
"room":"b",
"locations":[
{
"x":0,
"y":0
},
{
"x":0,
"y":0
},
{
"x":0,
"y":0
},
{
"x":0,
"y":0
},
{
"x":0,
"y":0
},
{
"x":0,
"y":0
}
]
}
So my query is how do I index through my nested data, so it reads ALL of the x & y values rather than just one & affect my drag behavior.
Help would be much appreciated.
thanks
Found a solution. Pretty Simple! Don't know why I didnt think of it before!
"translate("+data.locations[i].x+","+data.locations[i].y+")"\
I believe that is it. if anyone has another solution I'm open to listen.