d3 accessor of hierarchical array of objects - d3.js

I'm feeding data to d3 via json in a format that looks like this:
[
{
"outcome_id":22,
"history":[
{
"time":"2013-05-06T16:38:55+03:00",
"balance_of_power":0.2
},
{
"time":"2013-05-07T00:38:55+03:00",
"balance_of_power":0.2222222222222222
},
{
"time":"2013-05-07T08:38:55+03:00",
"balance_of_power":0.36363636363636365
}
],
"winner":true,
"name":"Pauline"
},
{
"outcome_id":23,
"history":[
{
"time":"2013-05-06T16:38:55+03:00",
"balance_of_power":0.2
},
{
"time":"2013-05-07T00:38:55+03:00",
"balance_of_power":0.1111111111111111
},
{
"time":"2013-05-07T08:38:55+03:00",
"balance_of_power":0.09090909090909091
}
],
"winner":false,
"name":"Romain"
}
]
I use this data to draw both a multiple series line chart (to show the evolution of "balance_of_power" through time) and a donut chart to represent the latest value of "balance_of_power" for all series.
So each top-level array element is an object that has several attributes, one of them being "history", which is itself an array of objects (that have the time and balance_of_power attributes).
A working example can be found here.
To produce the data for the donut chart I use a function that takes the latest element from each history array (the data is sorted by time) and generate another attribute that's called "last_balance".
For example the first element becomes:
{
"outcome_id":22,
"history":[...],
"winner":true,
"name":"Pauline",
"last_balance":0.36363636363636365
}
And then I specify the right accessor from the pie layout value:
pie = d3.layout.pie().value(function(d) { return d.latest_balance; })
Now I'd like to get rid of the extra step and change the accessor function so that I can read the value directly form the initial data structure and also be able to access any balance_of_power for a time given as an argument.
Is there a way to do that with only modifying the accessor of pie value ?
EDIT
I changed the .value function to this:
.value(function(d) {
var h = d.history[0];
d.history.forEach(function(elt, i, a) {
console.log("======"); // start debug
console.log("search for:"+selected_time.toString());
console.log("current value:"+d.history[i].time.toString());
console.log("test:"+(d.history[i].time == selected_time));
console.log("======"); // end debug
if(d.history[i].time == selected_time) {
h = d.history[i];
}
});
return h.balance_of_power;
})
But the comparison always fails, even when the values seem to be identical, so the previous code always returns the initial value.
Here's what the javascript console shows for the last iteration:
====== final_balance_donut_chart.js?body=1:11
search for:Thu Jun 06 2013 16:06:00 GMT+0200 (CEST) final_balance_donut_chart.js?body=1:12
current value:Thu Jun 06 2013 16:06:00 GMT+0200 (CEST) final_balance_donut_chart.js?body=1:13
test:false final_balance_donut_chart.js?body=1:14
======
EDIT 2
For some reason I had to convert both times to string to make this work.
Here is the final code fore .value:
.value(function(d) {
var h = d.history[0];
d.history.forEach(function(elt) {
if(elt.time.toString() == selected_time.toString()) {
h = elt;
}
});
return h.balance_of_power;
})

Yes, your code would look something like this.
time = "...";
pie = d3.layout.pie()
.value(function(d) {
var h = d.history[0];
for(var i = 0; i < d.history.length; i++) {
if(d.history[i].time == time) {
h = d.history[i];
break;
}
}
return h.balance_of_power;
});
You will need to handle the case when the time is not in the history though.

Related

dc.js Grouping for Bubble Chart Removing from wrong groups

I'm trying to create a bubble chart with dc.js that will have a bubble for each data row and will be filtered by other charts on the same page. The initial bubble chart is created correctly, but when items are filtered from another chart and added or removed from the group it looks like they are being applied to the wrong group. I'm not sure what I'm messing up on the grouping or dimensions. I've created an example fiddle here
There's simple pie chart to filter on filterColumn, a bubble chart that uses identifer1, a unique field, as the dimension and xVal, yVal, and rVal to display the data, and a dataTable to display the current records.
I've tried other custom groups functions, but switched to the example from the FAQ and still had problems.
var
filterPieChart=dc.pieChart("#filterPieChart"),
bubbleChart = dc.bubbleChart('#bubbleChart'),
dataTable = dc.dataTable('#data-table');
var
bubbleChartDim=ndx.dimension(dc.pluck("identifier1")),
filterPieChartDim=ndx.dimension(dc.pluck("filterColumn")),
allDim = ndx.dimension(function(d) {return d;});
var filterPieChartGroup=filterPieChartDim.group().reduceCount();
function reduceFieldsAdd(fields) {
return function(p, v) {
fields.forEach(function(f) {
p[f] += 1*v[f];
});
return p;
};
}
function reduceFieldsRemove(fields) {
return function(p, v) {
fields.forEach(function(f) {
p[f] -= 1*v[f];
});
return p;
};
}
function reduceFieldsInitial(fields) {
return function() {
var ret = {};
fields.forEach(function(f) {
ret[f] = 0;
});
return ret;
};
}
var fieldsToReduce=['xVal', 'yVal', 'rVal'];
var bubbleChartGroup = bubbleChartDim.group().reduce(
reduceFieldsAdd(fieldsToReduce),
reduceFieldsRemove(fieldsToReduce),
reduceFieldsInitial(fieldsToReduce)
);
filterPieChart
.dimension(filterPieChartDim)
.group(filterPieChartGroup)
...
;
bubbleChart
.dimension(bubbleChartDim)
.group(bubbleChartGroup)
.keyAccessor(function (p) { return p.value.xVal; })
.valueAccessor(function (p) { return p.value.yVal; })
.radiusValueAccessor(function (p) { return p.value.rVal; })
...
;
This was a frustrating one to debug. Your groups and reductions are fine, and that's the best way to plot one bubble for each row, using a unique identifier like that.
[It's annoying that you have to specify a complicated reduction, when the values will be either the original value or 0, but the alternatives aren't much better.]
The reductions are going crazy. Definitely not just original values and zero, some are going to other values, bigger or negative, and sometimes clicking a pie slice twice does not even return to the original state.
I put breakpoints in the reduce functions and noticed, as you did, that the values were being removed from the wrong groups. How could this be? Finally, by logging bubbleChartGroup.all() in a filtered handler for the pie chart, I noticed that the groups were out of order after the first rendering!
Your code is fine. But you've unearthed a new bug in dc.js, which I filed here.
In order to implement the sortBubbleSize feature, we sort the bubbles. Unfortunately we are also sorting crossfilter's internal array of groups, which it trusted us with. (group.all() returns an internal data structure which must never be modified.)
The fix will be easy; we just need to copy the array before sorting it. You can test it out in your code by commenting out sortBubbleSize and instead supplying the data function, which is what it does internally:
bubbleChart.data(function (group) {
var data = group.all().slice(0);
if (true) { // (_sortBubbleSize) {
// sort descending so smaller bubbles are on top
var radiusAccessor = bubbleChart.radiusValueAccessor();
data.sort(function (a, b) { return d3.descending(radiusAccessor(a), radiusAccessor(b)); });
}
return data;
});
Notice the .slice(0) at the top.
Hope to fix this in the next release, but this workaround is pretty solid in case it takes longer.
Here is a fiddle demonstrating the workaround.

Using Custom reduce function with Fake Groups

I have line chart where I need to show frequency of order executions over the course of a day. These orders are grouped by time interval, for example every hour, using custom reduce functions. There could be an hour interval when there were no order executions, but I need to show that as a zero point on the line. I create a 'fake group' containing all the bins with a zero count...and the initial load of the page is correct.
However the line chart is one of 11 charts on the page, and needs to be updated when filters are applied to other charts. When I filter on another chart, the effects on this particular frequency line chart are incorrect. The dimension and the 'fake group' are used for the dc.chart.
I put console.log messages in the reduceRemove function and can see that there is something wrong...but not sure why.
Any thoughts on where I could be going wrong.
FrequencyVsTimeDimension = crossfilterData.dimension(function (d) { return d.execution_datetime; });
FrequencyVsTimeGroup = FrequencyVsTimeDimension.group(n_seconds_interval(interval));
FrequencyVsTimeGroup.reduce(
function (p, d) { //reduceAdd
if (d.execution_datetime in p.order_list) {
p.order_list[d.execution_datetime] += 1;
}
else {
p.order_list[d.execution_datetime] = 1;
if (d.execution_type !== FILL) p.order_count++;
}
return p;
},
function (p, d) { //reduceRemove
if (d.execution_type !== FILL) p.order_count--;
p.order_list[d.execution_datetime]--;
if (p.order_list[d.execution_datetime] === 0) {
delete p.order_list[d.execution_datetime];
}
return p;
},
function () { //reduceInitial
return { order_list: {}, order_count: 0 };
}
);
var FrequencyVsTimeFakeGroup = ensure_group_bins(FrequencyVsTimeGroup, interval); // function that returns bins for all the intervals, even those without data.

D3 Zoomable Treemap changing the children accessor

I am trying to use Mike Bostock's zoomable treemap http://bost.ocks.org/mike/treemap/ with one modification. Instead of using nested JSON data, I have have a simple mapping from parents to a list of children. I built a function, getChildren(root), that simply returns root's children, or null if root does not have any children.
I have tried replacing all instances of d.children() with getChildren(d) in the treemap javascript file, but it seems that it is not working properly.
The resulting page shows the orange bar as normal up top, but nothing else displays correctly (i.e. there are no rectangles underneath the orange bar, just empty gray space). All the text from the children is mashed up in the top left corner of the empty gray space, so it might be that coordinates are not being assigned correctly.
Any ideas??
Thanks!
It looks like there were a few issues here:
Your data structure doesn't seem to be referencing the child nodes:
var nMap = {};
nMap.papa = {};
nMap.papa["children"] = [];
nMap.papa["children"].push({
"name": "c1"
});
// snip
nMap.c1 = {
size: 5
};
Unless I'm missing something, your getChildren function gets the { name: "c1" } object but never looks up nMap.c1. I'm not exactly certain what your alternative data structure is trying to achieve, but it seems like the most obvious option is to use a flat map of nodes, with children referenced by id, like this:
var nMap = {};
nMap.c1 = {
name: "c1",
value: 5
};
nMap.c2 = {
name: "c2",
value: 5
};
nMap.c3 = {
name: "c3",
value: 5
};
nMap.papa = {
name: "papa",
children: ['c1', 'c2', 'c3']
};
With a structure like this, you can map to the real children in the getChildren function:
function getChildren(par){
var parName = par.name,
childNames = parName in nMap && nMap[parName].children;
if (childNames) {
// look up real nodes
return childNames.map(function(name) { return nMap[name]; });
}
}
Your children were using size instead of value to indicate weight, and the rest of the code expected value (so they all had weight 0).
Because you're using the "zoomable" treemap approach, which uses a specialized version of the treemap layout, you don't need to specify the .children accessor of the treemap layout. Instead, use your custom accessor in the the custom layout helper:
function layout(d) {
// get the children with your accessor
var children = getChildren(d);
if (children && children.length > 0) {
treemap.nodes({ children: children });
children.forEach(function(c) {
c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
layout(c);
});
}
}
Working fiddle here: http://jsfiddle.net/nrabinowitz/WpQCy/

Extending dc.js to add a "simpleLineChart" chart

edit See here for the non-working example of what I'm trying to do: http://bl.ocks.org/elsherbini/5814788
I am using dc.js to plot data collected from bee hives at my university. I am pushing new data to the graphs on every database change (using the magic of Meteor). When the database is over 5000 records or so, rerendering the lines gets really slow. So I want to use simplify.js to preprocess the lines before rendering. To see what I'm talking about, go to http://datacomb.meteor.com/. The page freezes after a couple of seconds, so be warned.
I have started to extend dc.js with a simpleLineChart, which would inherit from the existing dc.lineChart object/function. Here is what I have so far:
dc.simpleLineChart = function(parent, chartGroup) {
var _chart = dc.lineChart(),
_tolerance = 1,
_highQuality = false,
_helperDataArray;
_chart.tolerance = function (_) {
if (!arguments.length) return _tolerance;
_tolerance = _;
return _chart;
};
_chart.highQuality = function (_) {
if (!arguments.length) return _highQuality;
_highQuality = _;
return _chart;
};
return _chart.anchor(parent, chartGroup);
}
simplify.js takes in an array of data, a tolerance, and a boolean highQuality, and returns a new array with fewer elements based on it's simplification algorithm.
dc.js uses crossfilter.js. dc.js charts are associated with a particular crossfilter dimension and group. Eventually, it uses the data from someGroup().all() as the data to pass to a d3.svg.line(). I can't find where this is happening in the dc.js source, but this is where I need to intervene. I want to find this method, and override it in the dc.simpleLineChart object that I am making.
I was thinking something like
_chart.theMethodINeedToOverride = function(){
var helperDataArray = theChartGroup().all().map(function(d) { return {
x: _chart.keyAccessor()(d),
y: _chart.valueAccessor()(d)};})
var simplifiedData = simplify(helperDataArray, _tolerance, _highQuality)
g.datum(simplifiedData); // I know I'm binding some data at some point
// I'm just not sure to what or when
}
Can anyone help me either identify which method I need to override, or even better, show me how to do so?
dc.js source: https://github.com/NickQiZhu/dc.js/blob/master/dc.js
edit:
I think I may have found the function I need to override. The original function is
function createGrouping(stackedCssClass, group) {
var g = _chart.chartBodyG().select("g." + stackedCssClass);
if (g.empty())
g = _chart.chartBodyG().append("g").attr("class", stackedCssClass);
g.datum(group.all());
return g;
}
And I have tried to override it like so
function createGrouping(stackedCssClass, group) {
var g = _chart.chartBodyG().select("g." + stackedCssClass);
if (g.empty())
g = _chart.chartBodyG().append("g").attr("class", stackedCssClass);
var helperDataArray = group().all().map(function(d) { return {
x: _chart.keyAccessor()(d),
y: _chart.valueAccessor()(d)};})
var simplifiedData = simplify(helperDataArray, _tolerance, _highQuality)
g.datum(simplifiedData);
return g;
}
However, when I make a simpleLineChart, it is just a linechart with a tolerance() and highQuality() method. See here: http://bl.ocks.org/elsherbini/5814788
Well, I pretty much did what I set out to do.
http://bl.ocks.org/elsherbini/5814788
The key was to not only modify the createGrouping function, but also the lineY function in the code. (lineY gets set to tell the d3.svg.line() instance how to set the y value of a given point d)
I changed it to
var lineY = function(d, dataIndex, groupIndex) {
return _chart.y()(_chart.valueAccessor()(d));
};
The way lineY was written before, it was looking up the y value in an array, rather than using the data bound to the group element. This array had it's data set before i made my changes, so it was still using the old, pre-simplification data.

Draw multiple line in jqplot using json Data

Iam trying to Draw multiple line in jqplot using json Data
I have doubt on jqPlot, i am using json data to plot a chart in jqPlot.I have two sets
of json data like this
var jsonData1 = {"PriceTicks": [{"Price":5.5,"TickDate":"Jan"},
{"Price":6.8,"TickDate":"Mar"},
{"Price":1.9,"TickDate":"June"},
{"Price":7.1,"TickDate":"Dec"}]};
var jsonData2 = {"PriceTicks": [{"Price":1.5,"TickDate":"Jan"},
{"Price":2.8,"TickDate":"Feb"},
{"Price":3.9,"TickDate":"Apr"},
{"Price":9.1,"TickDate":"Dec"}]};
suppose i have a javascript arrays i can call like this
$.jqplot('chartdiv', [cosPoints, sinPoints, powPoints1, powPoints2, powPoints3],
{
title:'Line Style Options',
seriesDefaults: {
showMarker:false,
},
}
);
where cosPoints,sinePoints etc are the javascript arrays in the similar way i have to
call the json data , i Used the following way it is failed ,please help me.
$.jqplot('jsonChartDiv', [jsonData1,jsonData2] {
title:'Fruits',
series:[{showMarker:false}],
dataRenderer: $.jqplot.ciParser,
axes: {
xaxis: {
renderer:$.jqplot.CategoryAxisRenderer,
}
},
});
I didn't manage to get the ciParser Renderer Plugin to work with your Data Format Requirement.
In the following jsFiddle you see a working example to convert your JSON objects to the required datastructure which is needed to display a Line Chart.
JSFiddle
// get array with both series
arSeries = [jsonData1, jsonData2];
// initalizat the output array for jqPlot
arJQ = [];
// for each Data Series
for (var z = 0; z < arSeries.length; z++)
{
// array for the prices
jqPrices = [];
var prices = arSeries[z].PriceTicks;
for (var i = 0; i < prices.length; i++)
{
jqPrices.push([prices[i].TickDate.toString(), prices[i].Price]);
}
// Result [["Jan",1.5],["Feb",2.8],["Apr",3.9],["Dec",9.1]]
// add to series array
arJQ.push(jqPrices);
}
To prevent the line going backward you should spedify either your date more granular or with the same tickes on each data record.

Resources