Get all dom nodes from d3 selection - d3.js

selection.node() returns only the first node. Can we get an array of all nodes from a selection?
EDIT Added some code to help us.
The attempt with each() is the only one producing the wanted
output, although quite verbose.
Calling sel[0] also returns an array with DOM nodes, but it's hacky (depends on the internal structure of the library) and includes an unwanted "parentNode" field.
// creating a selection to experiment with
var data= [1,2,3,4]
var sel = d3.select("li")
.data(data)
.enter().append("li").html(identity);
function identity(d){return d}
console.log(sel); // array[1] with array[4] with the <li>'s
// using .node()
var res1 = sel.node();
console.log(res1); // first <li> only
// using .each() to accumulate nodes in an array
var res2 = [];
function appendToRes2(){
res2.push(this);
}
sel.each(appendToRes2);
console.log(res2); // array[4] with the <li>'s (what I want)
// calling sel[0]
var res3 = sel[0];
console.log(res3); // array[4] with the <li>'s plus a "parentNode"
// #thisOneGuy's suggestion
var res4 = d3.selectAll(sel);
console.log(res4); // array[1] with array[1] with array[4] with the <li>'s
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDIT 2 Why do I want to do that?
To call array methods like reduce and map on the DOM nodes. D3 provides filter but to use others I first need to extract the node array from the selection.

I originally wrote this as a comment, but decided to turn it into an answer...
It looks like d3 v4 will include the functionality you want. If you don't want to wait, you can steal the implementation now and add it to the selection prototype:
d3.selection.prototype.nodes = function(){
var nodes = new Array(this.size()), i = -1;
this.each(function() { nodes[++i] = this; });
return nodes;
}
Usage example:
d3.selection.prototype.nodes = function(){
var nodes = new Array(this.size()), i = -1;
this.each(function() { nodes[++i] = this; });
return nodes;
}
var data= [1,2,3,4]
var sel = d3.select("li")
.data(data)
.enter().append("li").html(identity);
function identity(d){return d}
console.log(sel.nodes());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Since it came from #mbostock, it's a good bet is the best implementation available.

Related

D3 stack() vs nested objects

I'm running into an issue when trying to implement a normalized stacked bar chart using D3v4.
The problem occurs due to my data format which contains nested object arrays populated dynamically on the server side.
var data = [{x:"data1", y:[{name:"red", value:10}, {name:"green", value:20}]},
{x:"data2", y:[{name:"red", value:30}, {name:"green", value:5}]}];
Calling d3.stack() on this will not work since d3 doesn't know how to traverse into the object array y. (https://jsfiddle.net/xv1qgqjg/)
Is there any way to tell d3.stack() where to find the relevant data similar to the .data(function(d){ return d.y; }) used elsewhere?
It doesn't seem to be possible. According to the documentation regarding stack(data[, arguments…]),
Any additional arguments are arbitrary; they are simply propagated to accessors along with the this object.
Thus, you'll have to change your data, creating an array which you can pass to d3.stack(), such as this:
[{red:10,green:20},
{red:30,green:5}]
Given the data array in your question, there are several ways for creating the above-mentioned array. Here is my solution (the new array is called newData):
newData = [];
data.forEach(d => {
var tempObj = {}
d.y.forEach(e => {
tempObj[e.name] = e.value;
})
newData.push(tempObj);
});
Here is a demo:
var data = [{x:"data1", y:[{name:"red", value:10}, {name:"green", value:20}]},
{x:"data2", y:[{name:"red", value:30}, {name:"green", value:5}]}];
newData = [];
data.forEach(d => {
var tempObj = {}
d.y.forEach(e => {
tempObj[e.name] = e.value;
})
newData.push(tempObj);
});
var stack = d3.stack()
.keys(["red", "green"])
.order(d3.stackOrderNone)
.offset(d3.stackOffsetExpand);
var series = stack(newData);
console.dir(series);
<script src="https://d3js.org/d3.v4.min.js"></script>

Maps API: Finding the longest common path in two given paths

Google maps and Bing maps have methods that can give the directions from point A to point B on a map. This highlights a path from A to B on the map - call this P1
Suppose, P2 is another path from C to D (some other points), how can we find the longest common length of path between paths P1 and P2?
You have plenty of ways to do what you want.
Curiously, I tried to do it using JavaScript only and to do so, I used JSTS library that would compute the intersection between two routes (in my case, the geometry were retrieved from Bing, but I did not include the request in this example as it's not helpful).
Use case:
So, you want to have the common path between two paths (or the part of a route where you can use car-sharing or where you can run with your friend for example), if this is correct, then this example will help you.
Libraries:
First, the following library is need: JSTS, you can get it through Github dedicated repository: https://github.com/bjornharrtell/jsts
On other interesting library is Turf available here: https://github.com/Turfjs/
Implementation with JSTS and leaflet:
Here is the piece of JavaScript that will be interesting in this case:
<script type="text/javascript">
var routeCoordinatesA = [[50.619512, 3.061242]....TRUNCATED FOR READIBILITY** ];
var routeCoordinatesB = [[50.619512, 3.061242]....TRUNCATED FOR READIBILITY** ];
$(function () {
var map = L.map('map').setView([47.5, 2.75], 5);
// Add base tile layer - sample from Leaflet website
L.tileLayer('http://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var polylineA = L.polyline(routeCoordinatesA, { color: '#4b98dc' }).addTo(map);
var polylineB = L.polyline(routeCoordinatesB, { color: '#de6262' }).addTo(map);
var geometryFactory = new jsts.geom.GeometryFactory();
// Coordinates adapted to match for jsts
var coordsA = [];
$.each(routeCoordinatesA, function (idx, current) { coordsA.push([current[1], current[0]]); });
var coordsB = [];
$.each(routeCoordinatesB, function (idx, current) { coordsB.push([current[1], current[0]]); });
// Element A
var coordinatesA = bindCoord2JTS(coordsA);
var shellA = geometryFactory.createLinearRing(coordinatesA);
var jstsPolygonA = geometryFactory.createPolygon(shellA);
// Element b
var coordinatesB = bindCoord2JTS(coordsB);
var shellB = geometryFactory.createLinearRing(coordinatesB);
var jstsPolygonB = geometryFactory.createPolygon(shellB);
// Interection
var bufferTolerance = (2 / 1000); // Small buffer to avoid different node no detection
var intersection = shellA.buffer(bufferTolerance).intersection(shellB);
var intersectionPoints = [];
$.each(intersection.getCoordinates(), function (idx, current) {
intersectionPoints.push([current.x, current.y]);
});
intersectionPoints.pop();
var intersectionLine = L.polyline(intersectionPoints, { color: '#4fc281', weight: 8 }).addTo(map);
map.fitBounds(routeCoordinatesA.concat(routeCoordinatesB));
});
var bindCoord2JTS = function (coords) {
var coordinates = [];
for (var i = 0; i < coords.length; i++) {
coordinates.push(new jsts.geom.Coordinate(
coords[i][1], coords[i][0]));
}
return coordinates;
};
You can grab all the working example among my Leaflet experiments available on Github as well:
https://github.com/nicoboo/maps/tree/master
And here the page that implements what I was talking about:
https://github.com/nicoboo/maps/blob/master/Boo.Maps.Web.LeafletExperiments/LeafletWithin/index.html
Here for the live demo: http://htmlpreview.github.io/?https://github.com/nicoboo/maps/blob/master/Boo.Maps.Web.LeafletExperiments/LeafletWithin/index.html
Considerations:
Of course, this is really based on the client side and it might be usefull to have the information on the server-side, I would recommend to use a spatially enabled database so you can use the STBuffer() and STIntersection() methods directly on the column or results that you manipulate with the best performances.
I am not sure to fully understand your request but both Bing maps ans Google maps API for the directions contains in their response a "distance" field which specifies the value of the directions.
Here are two links for both documentation:
Bing Maps & Google Maps
With that you could compare the distance value between the two path and find the longest.
Hope this help.

Export crossfilter dataset to excel in dc.js

I made a visualization page using crossfilter.js and dc.js . I want to export the filtered dataset to excel. Is any way to do this.?
I think the best way to do this is to create another dimension and then call dimension.top(Infinity) to get all the records (sorted by that dimension's key).
Jacob Rideout created a pull request for a new method to do just this without the overhead, but it was not accepted (doesn't look like it was rejected either ;):
https://github.com/square/crossfilter/pull/95
But I doubt you will notice any performance penalty for creating the extra dimension. (Please comment on that PR if you do!)
function groupArrayAdd(keyfn) {
var bisect = d3.bisector(keyfn);
return function (elements, item) {
var pos = bisect.right(elements, keyfn(item));
elements.splice(pos, 0, item);
return elements;
};
}
function groupArrayRemove(keyfn) {
var bisect = d3.bisector(keyfn);
return function (elements, item) {
var pos = bisect.left(elements, keyfn(item));
if (keyfn(elements[pos]) === keyfn(item))
elements.splice(pos, 1);
return elements;
};
}
function groupArrayInit() {
return [];
}
var facts = crossfilter(data); //pass your mater dataset here.
var filteredRows = facts.groupAll().reduce(
groupArrayAdd(dc.pluck('shift')),
groupArrayRemove(dc.pluck('shift')),
groupArrayInit}
);
filteredRows.value() will give you the crossfilted data. Every time the data is filteded, this function will give automatically five the filted output which you can use to export to excel using any jquery plugin.
Another way to find out filtered data is using below dc function:
dimension.top(Infinity)

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.

Resources