D3.js rollup on nesting operator with multiple keys - javascript

I am working on a d3.js visualization for a time reporting application.
I have row data in an array actuals containing project time reports (simplified):
[{ resource: "John Smith",
reporting_period: "2012/04/1",
project: "Java implementation",
hours: 8}
... }]
I am trying to use the d3.nest operator to group the project actuals hierarchically by project, resource and period. Everything works great but I cannot find a way to get the subtotals of hours at the intermediate levels of the grouping using the nest.rollup() operator.
I have something like:
actuals_by_prj_rsrc_period = d3.nest()
.key(function(d) { return d["project"]; })
.key(function(d) { return d["resource"]; })
.key(function(d) { return d["reporting_period"]; })
.rollup(function(rows) {return {
tot_hours:d3.sum(rows, function(d) {return d["hours"];}),
actuals: rows
};})
.entries(actuals);
but it returns tot_hours only at the leaf level.
Any advice on how to approach this using only d3.nest?

from docs:
nest.rollup(function)
Specifies a rollup function to be applied on each group of leaf
elements. The return value of the rollup function will replace the
array of leaf values in either the associative array returned by the
map operator, or the values attribute of each entry returned by the
entries operator.
As you can see rollup works with leaf elements. You could bypass this by having data nested at multiple levels:
function nest(keys, data, rollupFunc) {
var nst = d3.nest();
keys.forEach(function(key) {
nst.key(function(d) { return d[key]; });
});
if (rollupFunc) {
nst.rollup(rollupFunc);
}
return nst.entries(data);
}
var rollupFunction = function(d) {
return {
"total_hours": d3.sum(d, function(dd) { return dd["hours"]})
}
}
var rez1 = nest(["project", "resource"], actuals);
var rez2 = nest(["project"], actuals, rollupFunction);
var rez3 = nest(["project", "resource"], actuals, rollupFunction);
But this very inefficient for larger data sets. Otherwise I would suggest using nest() function to create all intermediate levels. Then you could aggregate total hours using your own recursive function. Pseudocode:
function aggregate(node) {
if (node has property total_hours) {
return total_hours
}
sum = 0
foreach child in data.values {
sum += aggregate(child)
}
node.total_hours = sum
return node.total_hours
}

Related

Reshape data for D3 stacked bar chart

I have some csv data of the following format, that I have loaded with d3.csv():
Name, Group, Amount
Bill, A, 25
Bill, B, 35
Bill, C, 45
Joe, A, 5
Joe, B, 8
But as I understand from various examples, I need the data like this to use it in a stacked bar chart:
Name, AmountA, AmountB, AmountC
Bill, 25, 35, 45
Joe, 5, 8, NA
How can I transform my data appropriately in the js script? There is also the issue of missing data, as you can see in my example.
Thanks for any help.
Yes, you are correct that in order to use d3.stack your data needs re-shaping. You could use d3.nest to group the data by name, then construct an object for each group - but your missing data will cause issues.
Instead, I'd do the following. Parse the data:
var data = `Name,Group,Amount
Bill,A,25
Bill,B,35
Bill,C,45
Joe,A,5
Joe,B,8`;
var parsed = d3.csvParse(data);
Obtain an array of names and an array of groups:
// obtain unique names
var names = d3.nest()
.key(function(d) { return d.Name; })
.entries(parsed)
.map(function(d) { return d.key; });
// obtain unique groups
var groups = d3.nest()
.key(function(d) { return d.Group; })
.entries(parsed)
.map(function(d) { return d.key; });
(Note, this is using d3.nest to create an array of unique values. Other utility libraries such as underscore have a simpler mechanism for achieving this).
Next, iterate over each unique name and add the group value, using zero for the missing data:
var grouped = names.map(function(d) {
var item = {
name: d
};
groups.forEach(function(e) {
var itemForGroup = parsed.filter(function(f) {
return f.Group === e && f.Name === d;
});
if (itemForGroup.length) {
item[e] = Number(itemForGroup[0].Amount);
} else {
item[e] = 0;
}
})
return item;
})
This gives the data in the correct form for use with d3.stack.
Here's a codepen with the complete example:
https://codepen.io/ColinEberhardt/pen/BQbBoX
It also makes use of d3fc in order to make it easier to render the stacked series.

Multilevel grouping in linq js

I have this json Format
var personArray =
[
{name:"person1",code:"101011",mainDept:"mainD 1",dept:"dept1",SubDept:"Sub01"},
{name:"person2",code:"201012",mainDept:"mainD 1",dept:"dept1",SubDept:"Sub11"},
{name:"person3",code:"301013",mainDept:"mainD 1",dept:"dept2",SubDept:"Sub12"},
{name:"person4",code:"401014",mainDept:"mainD 1",dept:"dept2",SubDept:"Sub12"},
{name:"person5",code:"501015",mainDept:"mainD 1",dept:"dept2",SubDept:"Sub13"},
{name:"person6",code:"601116",mainDept:"mainD 1",dept:"dept3",SubDept:"Sub21"},
{name:"person7",code:"701117",mainDept:"mainD 1",dept:"dept3",SubDept:"Sub21"},
{name:"person8",code:"801118",mainDept:"mainD 1",dept:"dept4",SubDept:"Sub22"},
{name:"person9",code:"901119",mainDept:"mainD 2",dept:"dept12",SubDept:"Sub23"},
{name:"person10",code:"101111",mainDept:"mainD 2",dept:"dept12",SubDept:"Sub24"},
{name:"person12",code:"121012",mainDept:"mainD 2",dept:"dept13",SubDept:"Sub25"},
{name:"person13",code:"131013",mainDept:"mainD 2",dept:"dept131",SubDept:"Sub26"},
{name:"person14",code:"141014",mainDept:"mainD 3",dept:"dept132",SubDept:"Sub27"},
{name:"person15",code:"151015",mainDept:"mainD 3",dept:"dept132",SubDept:"Sub27"},
{name:"person16",code:"161116",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub1"},
{name:"person17",code:"171117",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub1"},
{name:"person18",code:"181118",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub1"},
{name:"person21",code:"211012",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub1"},
{name:"person22",code:"221013",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub001"},
{name:"person23",code:"231014",mainDept:"mainD 4",dept:"dept151",SubDept:"Sub002"},
{name:"person24",code:"241015",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person25",code:"251116",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person26",code:"261117",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person27",code:"271118",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person28",code:"281119",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person29",code:"291119",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"}];
and i want to build data for jsTree (https://www.jstree.com/docs/json/)
multi level grouping will be like this i.e mainDept -> dept - > SubDept - > person
i tried this to get one level grouping but cant figure out how to get multi level grouping.
var linq = Enumerable.From(personArray);
var grp = linq.GroupBy("$.mainDept","{text:$.dept}","{name:$,children:$$.ToArray()}").ToArray()
There really isn't a nice way to an arbitrarily deeply nested grouping, particularly if you need to do something different at each level. Doing some sort of recursion makes this simple. Fortunately Linq.js has a Let() function to allow for this. With some specially crafted functions, this could be done rather nicely.
function grouper(propertyName, selector) {
return function (e) {
return e.GroupBy("$." + propertyName, null, function (k, g) {
return {
text: k,
children: g.Let(selector).ToArray()
};
});
};
}
var items = Enumerable.From(personArray)
.Let(grouper('mainDept', function (g1) {
return g1.Let(grouper('dept', function (g2) {
return g2.Let(grouper('SubDept', function (g3) {
return g3.Select("$.name").ToArray();
}));
}));
}))
.ToArray();
fiddle
For a different approach, you utilize jstree's alternate form of coming up with the parent/child relationships. You don't need to nest anything anymore, just come up with a flat list of the config nodes.
var items = Enumerable.From(personArray)
.Let(function (e) {
var roots = { '#': {}, mainDept: {}, dept: {}, SubDept: {} };
e.ForEach(function (p) {
roots['#'][p.mainDept] = '#';
roots['mainDept'][p.dept] = p.mainDept;
roots['dept'][p.SubDept] = p.dept;
roots['SubDept'][p.name] = p.SubDept;
});
function makeNode(root) {
return Enumerable.From(roots[root]).Select("{ parent: $.Value, id: $.Key, text: $.Key }");
}
return makeNode('#').Concat(makeNode('mainDept')).Concat(makeNode('dept')).Concat(makeNode('SubDept'));
})
.ToArray();
fiddle
First you have to parse your personArray into json acceptable by jsTree and then feed it to jsTree initialisation. I can't help you with linq, but with plain javascript it could work like in this demo - Fiddle.

ignoring a group with the D3 nest function

I have some data where, in a given column of a csv, there are six possible values:
1,2,3,4,5,NaN.
I am currently trying to group the data using the d3.nest and rollup functions. My goal is to group the data but exclude "NaN" values in the final output.
This is my current code:
var nested = d3.nest()
.key(function(d){return d[question];
})
.rollup(function(leaves){
var total = data.length
var responses = leaves.length;
return {
'responses' : responses,
'percent' : responses/total
};
})
.entries(data)
As you can see, I would like to return both a count of each of the categories as well as the percentage of the total that they represent. After removing NaN, I would also like the removal of NaN represented in percentage values of all of the other categories so that they sum to 100%.
The easiest way to do this is to remove the rows the contain NaN before passing the data to d3.nest():
var filtered = data.filter(function(d) { return d.question !== 'NaN'; });

Recursively (or iteratively) make a nested html table with d3.js?

I have an array of nested JSON structures where they have varying depth and not the same set of keys everywhere:
[
{
"name":"bob",
"salary":10000,
"friends":[
{
"name": "sarah",
"salary":10000
},
{
"name": "bill",
"salary":5000
}
]
},
{
"name":"marge",
"salary":10000,
"friends":[
{
"name": "rhonda",
"salary":10000
},
{
"name": "mike",
"salary":5000,
"hobbies":[
{
"name":"surfing",
"frequency":10
},
{
"name":"surfing",
"frequency":15
}
]
}
]
},
{
"name":"joe",
"salary":10000,
"friends":[
{
"name": "harry",
"salary":10000
},
{
"name": "sally",
"salary":5000
}
]
}
]
I wanted to use D3 to render this as nested html tables. For example the friends column will have tables showing the name, and salary of the friends of the individual referenced in the row. Sometimes one of these tables will have another level of a sub table.
I imagine the way to do this is by recursively creating tables. I wrote a python program that takes a JSON structure like this, and renders tables within tables, and the easiest way to do that was recursively. I see on the d3.js documentation there is a .each() thing you can call, which I am sure is what I need, I just need a little boost getting there (https://github.com/mbostock/d3/wiki/Selections#wiki-each).
So is there a nice way to do this in D3? I found this great example for rendering a 2d matrix of data as a table Creating a table linked to a csv file. With that tutorial I was able to get the outer most level of this data-structure rendered as a table, but I am stuck on how to go into levels recursively as needed, as of now they just show up as "Object" in the table since I am not treating them differently from normal strings and numbers.
Also I found this other question/answer that is similar to my question, but I really don't understand javascript well enough to see where/how the recursion is happening and readapt the solution to fit my needs: How do I process data that is nested multiple levels in D3?. Any advice or pointers to tutorials on recursively or iteratively processing nested tree like JSON data-structures in D3 would be much appreciated!
A recursive function would probably be good approach. See code below for one possible implementation (assuming your data is stored in jdata). See the comments in the code for some explanation and see this Gist for a live version: http://bl.ocks.org/4085017
d3.select("body").selectAll("table")
.data([jdata])
.enter().append("table")
.call(recurse);
function recurse(sel) {
// sel is a d3.selection of one or more empty tables
sel.each(function(d) {
// d is an array of objects
var colnames,
tds,
table = d3.select(this);
// obtain column names by gathering unique key names in all 1st level objects
// following method emulates a set by using the keys of a d3.map()
colnames = d // array of objects
.reduce(function(p,c) { return p.concat(d3.keys(c)); }, []) // array with all keynames
.reduce(function(p,c) { return (p.set(c,0), p); }, d3.map()) // map with unique keynames as keys
.keys(); // array with unique keynames (arb. order)
// colnames array is in arbitrary order
// sort colnames here if required
// create header row using standard 1D data join and enter()
table.append("thead").append("tr").selectAll("th")
.data(colnames)
.enter().append("th")
.text(function(d) { return d; });
// create the table cells by using nested 2D data join and enter()
// see also http://bost.ocks.org/mike/nest/
tds = table.append("tbody").selectAll("tr")
.data(d) // each row gets one object
.enter().append("tr").selectAll("td")
.data(function(d) { // each cell gets one value
return colnames.map(function(k) { // for each colname (i.e. key) find the corresponding value
return d[k] || ""; // use empty string if key doesn't exist for that object
});
})
.enter().append("td");
// cell contents depends on the data bound to the cell
// fill with text if data is not an Array
tds.filter(function(d) { return !(d instanceof Array); })
.text(function(d) { return d; });
// fill with a new table if data is an Array
tds.filter(function(d) { return (d instanceof Array); })
.append("table")
.call(recurse);
});
}

Applying a date range filter to a crossfilter dimension

I'm attempting to use the crossfilter javascript library (in conjunction with D3.js) to group and filter json data.
My json data has the following fields: week_date, category, team_name, title, subtitle
I've been able to successfully group on all records to produce YTD totals, using something like this:
var dimension = data.dimension(function(d) { return d[target]; });
var dimensionGrouped = dimension.group().all();
Where target is either category or team_name.
In addition to keeping the YTD totals, I also want to display totals for a given range. For example, a user-selected week (i.e. Oct 1st - 5th).
How do I create a filtered group which returns the totals for a given date range? Using my week_date field.
Well, after some superficial research, including skimming over crossfilter's issues list, I've concluded that crossfilter does not currently support grouping on multiple fields.
I was able to work around this by using a filtered copy of the data instead, as such:
// YTD rows
var crossfilterData = crossfilter(data);
var ytdDimension = crossfilterData.dimension(function(d) { return d[target]; });
var ytdDimensionGrouped = ytdDimension.group().all();
ytdDimensionGrouped.forEach(function (item) {
// ...
});
// Ranged rows
var filteredData = data.filter(function (d) {
return d.week_date >= filter[0] && d.week_date <= filter[1];
});
crossfilterData = crossfilter(filteredData);
var rangeDimension = crossfilterData.dimension(function(d) { return d[target]; });
var rangeDimensionGrouped = rangeDimension.group().all();
rangeDimensionGrouped.forEach(function (item) {
// ...
});

Categories

Resources