Getting bound data without d3 info when using Force Layout - d3.js

I have the following code to handle double taps...
var onClick = function (e) {
if ((d3.event.timeStamp - last) < 500) {
return callback(e);
}
last = d3.event.timeStamp;
};
But this returns...
{
"uuid":"e2befb22-849b-4b56-91b4-8c297311491b",
"title":"JRG-024",
"weight":3,
"x":-4120.238083042915,
"y":2759.9261895569307,
"px":-4102.444457966419,
"py":2753.0045790866943
}
Notice the x,y,px,py, etc I want to avoid these and get the clean data that was originally bound. I also tried return callback(d3.select(this).data()[0]) but this still returns the location data. Is there a way to remove this data from the response, short of filtering?

Here's an example based on my comment
d3.select('body')
.selectAll('.data')
.data([10,20,30])
.enter()
.append('div')
.attr('class', 'data');
var node = d3.select('.data').node();
console.log(node.__data__);
The console output is 10

I ended up just putting everything in a property and passing it like that. Something similar to this...
var d3Data = data.map(function(item){
var result = {
...
originalData : item
}
return result
})
Not accepting because it is hacky hacky hacky.

Related

Merge selection with groups

I've worked out a consistent pattern for using the new selection merge which is brilliant for reusable charts where data and/or scales may change.
I've also been using the key function successfully.
However, I seem to get a problem when entering and appending a group with multiple elements. The data is successfully updated in the group but not the appended elements.
I've got round it by adding a fix (see below) but I'm sure it is something really obvious that needs to be changed to resolve it.
Any thoughts?
//define data group
var my_group = svg.selectAll(".data_group")
.data(my_data,function(d){return d.id});
//enter new groups
var enter = my_group.enter()
.append("g")
.attr("class","data_group");
//append items to group
enter.append("text").attr("class","group_item group_text")
enter.append("circle").attr("class","group_item group_circle");
//merge and remove
my_group.merge(enter);
my_group.exit().remove();
//fix added to reset changing data for bars.
d3.selectAll(".group_item").each(function(d){
d3.select(this)._groups[0][0].__data__ = d3.select(this)._groups[0][0].parentElement.__data__;
});
d3.selectAll(".group_text")
.... add properties to text - ie x,y,fill,text-anchor,text
d3.selectAll(".group_circle")
.... add properties to circle - ie cx,cy,fill,stroke,radius
There is absolutely no need for selecting the parent group, getting its data and rebinding it to the child elements, as the code in your question and the other answer do. This is bending over backwards. Also, do not delete/re-append elements, as suggested, which is not an idiomatic D3 approach.
The thing is simple: the new data is there for the children elements in the "enter" selection. You just need to use the parent's selection (with select()) to propagate them.
Here is a basic demo, using (most of) your code. The code generates from 1 to 5 data objects, with a random property called someProperty. You'll see that, using your each(), only the children elements in the "enter" selection are changed:
var svg = d3.select("svg");
d3.interval(function() {
var data = d3.range(1 + ~~(Math.random() * 4)).map(function(d) {
return {
id: "id" + d,
"someProperty": ~~(Math.random() * 100)
}
});
update(data);
}, 2000);
function update(my_data) {
var my_group = svg.selectAll(".data_group")
.data(my_data, function(d) {
return d.id
});
my_group.exit().remove();
var enter = my_group.enter()
.append("g")
.attr("class", "data_group");
enter.append("text").attr("class", "group_item group_text")
enter.append("circle").attr("class", "group_item group_circle");
my_group = my_group.merge(enter);
console.log("---")
d3.selectAll(".group_text").each(function(d) {
console.log(JSON.stringify(d))
});
}
.as-console-wrapper { max-height: 100% !important;}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
Now, if we use your parent's selection...
my_group.select(".group_text").each(function(d) {
console.log(d)
})
... you'll see that all properties are updated:
var svg = d3.select("svg");
d3.interval(function() {
var data = d3.range(1 + ~~(Math.random() * 4)).map(function(d) {
return {
id: "id" + d,
"someProperty": ~~(Math.random() * 100)
}
});
update(data);
}, 2000);
function update(my_data) {
var my_group = svg.selectAll(".data_group")
.data(my_data, function(d) {
return d.id
});
my_group.exit().remove();
var enter = my_group.enter()
.append("g")
.attr("class", "data_group");
enter.append("text").attr("class", "group_item group_text")
enter.append("circle").attr("class", "group_item group_circle");
my_group = my_group.merge(enter);
console.log("---")
my_group.select(".group_text").each(function(d) {
console.log(d)
})
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
Finally, in your now deleted answer you're using my_group.selectAll(). The problem is that selectAll() does not propagate the data.
Have a look at this table I made:
Method
select()
selectAll()
Selection
selects the first element that matches the selector string
selects all elements that match the selector string
Grouping
Does not affect grouping
Affects grouping
Data propagation
Propagates data
Doesn't propagate data
Pay attention to the propagates data versus doesn't propagate data.
The more d3 way of copying the data bound to the parent g elements
No need to add the fix
d3.selectAll(".group_text")
.datum(function () { return d3.select(this.parentNode).datum(); } )
// .... add properties to text - ie x,y,fill,text-anchor,text
d3.selectAll(".group_circle")
.datum(function () { return d3.select(this.parentNode).datum(); } )
// .... add properties to circle - ie cx,cy,fill,stroke,radius

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.

D3 - using enter() and exit() selections to update child elements

I have g.row elements containing g.cell elements, each containing a rect element. My nested data is bound to g.row and then g.cell. The rect elements access the data bound to g.cell.
At the moment my enter and exit selections add and remove g.cell. It would be more efficient to have them add and remove the rect elements, because g.cell has events bound to it that I need to reassign. But is this possible? I can't see how to get it to work.
I've managed to run cell.exit().selectAll("rect").remove(); which works fine. But cell.enter().selectAll("g.cell").append("rect"); throws an error ("[this code] is not a function"). While cell.enter().append("rect") doesn't append a rect.
Current code on g.cell:
var cell = row.selectAll("g.cell")
.data(function(d){
return d.value.filter(function(p){
if (p[1]=='') {
return horizNodesCopy.indexOf(p[0])!=-1;
} else {
return horizNodesCopy.indexOf(p[0]+' -- '+p[1])!=-1;
}
});
});
var cell2 = cell.enter().append("g")
.attr("class",function(d,i,j){ return "cell cell_"+i; })
.attr('transform',function(d,i,j){
if (d[1]=='') {
return 'translate('+ x(d[0]) +',0)';
} else {
return 'translate('+ x(d[0]+' -- '+d[1]) +',0)';
}
});
addRectangles(cell2,colorScale);
cell.exit().remove();
This feels like it's going to be something obvious :/

dc.js ignore results later than today

In DC.JS I have a rowChart of Top 10 variance of A minus B with the following dim/group:
.dimension(dateDim)
.group(grp)
Relevant Variables are:
var dateDim = ndx.dimension(function (d) { return d.DATE; });
var dateFormat = d3.time.format("%d/%m/%Y");
For the group, this works fine:
var grp= dateDim.group().reduceSum(function(d) {return Math.abs(d.A - d.B);});
However, what I'd like to do is only show items that are up to today only.
I tried the function below but this is not working. It may just be syntax.
Any Ideas?
var grp2= dateDim.group().reduceSum(function(d) {
if (dateDim > dateFormat(today))return dateDim; else return Math.abs(d.A- B.plan);
Thanks, stutray
This is addressed in the dc.js FAQ: https://github.com/dc-js/dc.js/wiki/FAQ#how-do-i-filter-the-data-before-its-charted
So ... originally I said the above. But what you actually want to do is filter out records that don't match a particular pre-defined filter so that they are not aggregated into your groups for this chart? In that case you need to use custom filter functions and I would recommend using a helper library like Reductio, which provides a filter function.
var dAct = reductio().filter(function(d) {
// Here `d` is the actual data record. Return a boolean by testing
// d against your filter criteria. Return true to include the record and
// false to exclude it.
return d.STARTDATE !== "10\/Sep\/2016";
})
.sum(function(d) {return Math.abs(d.A - d.B);})(dateDim.group());
If you use Reductio, you'll also need to use a valueAccessor function on your chart in dc.js:
rChart
...
.valueAccessor(function(d) { return d.value.sum; })
Updated example on Codepen: http://codepen.io/anon/pen/oxrPJv
Documentation: https://github.com/crossfilter/reductio#aggregations-standard-aggregations-reductio-b-filter-b-i-filterfn-i-

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.

Resources