Manually selecting a feature in a map generated by d3.js (for the purposes of zooming in this case) - d3.js

I'd like to implement a map that zooms in on an area similar to Mike's click-zoom-example http://bl.ocks.org/mbostock/2206590
In fact I have this working fine already. My problem is that I can't rely on the click event to implement the zoom — the zoom will be triggered by another event (a link). So when I get to this part of Mike's code:
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
...
I'm a bit of a loss as I don't have 'd'. So, I'm assuming that I can instead, manually pass 'd' to my click function when I call it. But how do I actually select the feature (which is what 'd' represents) I want from the map?
To be a bit more concrete, I have a map of the world. The paths within the SVG group contain class information (e.g. the one for France looks like):
<path class="subunit FXX FRA" id="FXX" data-subunit="FXX" data-countryName="France" data-countryCode="FRA" d="M153.88838704622088,519........"></path>
How would I pass the 'France object' to the clicked(d) function? Or is there another approach altogether that I should be trying.
Any tips or help greatly appreciated.

You can use D3's select for this purpose:
d3.select(".FRA").each(function(d) {
// same code as inside clicked
});

Get the data associated with the France object:
d3.select('.FXX.FRA').datum()
And pass it to clicked:
clicked(d3.select('.FXX.FRA').datum())

Related

amCharts 4 - How to Access Currently-Hovered Series Data/Color in XYChart in JavaScript

I'm trying to access the currently-hovered series data and color via JavaScript. The data is available to the legend and tooltip, but I'm not sure how to directly access it.
It's possible to place the legend in an external container, but their code creates a lot of additional containers/wrappers which makes formatting difficult. This Github question addresses it, but no answer was provided.
Perhaps events could be used to detect changes in the legend text or tspan elements and then grab the new text, but I'm not sure how to do this (using amCharts events) and how efficient it would be (especially with multiple series and/or charts with synced cursors).
Another idea was to get the data based on cursor position, but this seems inefficient (cursorpositionchanged fires too often - on mouse/cursor movement even when the series data hasn't changed). Maybe it could be done more efficiently based on change in dateAxis value? For example, using the positionchanged event listener:
chart.cursor.lineX.events.on('positionchanged', function() {
// get series data and do something with it
});
At least when using chart.cursor.xAxis = dateAxis, the positionchanged event only seems to fire when the cursor jumps to a new value. So it would be more efficient than an event that fired on mouse/cursor movement.
Any suggestions would be appreciated.
UPDATE
By currently-hovered, I am referring to the series data and color accessible via the tooltip (for example) with the mouse over the chart.
Examples: CandlestickSeries and LineSeries
One method you can try is to set an adapter for tooltipText on the object of concern. Since this may run multiple times especially via a chart cursor, perhaps keep track of changes to the tooltip via monitoring the unique value, e.g. in the samples provided that would be the date field. The data you're looking for can be found in the adapter's target.tooltipDataItem. The color, if on the series, will be target.tooltipDataItem.component.fill (in the case of the line series example, the target is the line series and has no change of color, so you can just use target.fill), otherwise e.g. in the case of CandleStick series the color would be on the candle stick, or column, i.e. via target.tooltipDataItem.column.fill.
Sample adapter for LineSeries:
var tooltipDate;
series.adapter.add("tooltipText", function(text, target) {
// data via target.tooltipDataItem.dataContext
console.log('text adapter; color: ', target.tooltipDataItem.component.fill.hex);
if (tooltipDate !== target.tooltipDataItem.dataContext.date) {
console.log('new tooltip date, do something');
tooltipDate = target.tooltipDataItem.dataContext.date;
}
// note: in this case: component === target
return text;
});
Demo:
https://codepen.io/team/amcharts/pen/9f621f6a0e5d0441fe55b99a25094e2b
Sample Candlestick series adapter:
var tooltipDate;
series.adapter.add("tooltipText", function(text, target) {
// data via target.tooltipDataItem.dataContext
console.log('text adapter; color: ', target.tooltipDataItem.column.fill.hex);
if (tooltipDate !== target.tooltipDataItem.dataContext.date) {
console.log('new tooltip date, do something');
tooltipDate = target.tooltipDataItem.dataContext.date;
}
return text;
});
Demo:
https://codepen.io/team/amcharts/pen/80343b59241b72cf8246c266d70281a7
Let us know if this is making sense, and if the adapter route is a good point in time to capture changes, data, color, as well as if it's efficient enough a manner to go about this.

dc.js Weird mouse zooming for seriesChart

I want to use the mouse zoom functionality on seriesChart and have it filter for other charts of the same group.
When I enable the zoom with .mouseZoomable(true) on seriesChart, and zoom the chart, the other charts become empty.
This doesn't happen when I enable it on a LineChart.
Here is a simple example: https://codepen.io/udeste/pen/ZKeXmX
(Zoom the second chart with the mouse. All is working. But when you zoom the first chart the other charts go blank.)
What am I doing wrong? Is it a dc.seriesChart bug?
It's because dc.seriesChart required you to supply that strange dimension, but it didn't change the filter function accordingly.
You specified seriesDimension like so:
var seriesDimension = ndx.dimension(function(d) {
return [+d.Expt, +d.Hours];
});
But when you zoom, the dc.coordinateGridMixin just filters using a regular dc.filters.RangedFilter, which does not know about these kinds of two-dimensional "multikeys".
Probably since the series chart requires this kind of input, it should redefine the filter handler to also deal with multikeys. But until then, you can work around it by providing your own filterHandler:
seriesChart.filterHandler(function(dimension, filters) {
if(filters.length === 0) // 1
dimension.filter(null);
else {
console.assert(filters.length===1); // 2
console.assert(filters[0].filterType==='RangedFilter');
dimension.filter(function(d) { // 3
return filters[0][0] <= d[1] && d[1] < filters[0][1];
})
}
});
What this does:
Checks if this event is because the filters have been cleared, and clears the dimension's filter if so.
Asserts that the filter is what is expected. coordinateGridMixin will always supply a single dc.filters.RangedFilter but who knows what else could happen.
Supplies a substitute filter function that checks if the part of the key used by the keyAccessor falls within the range (instead of comparing the array with the range, which will always return false).
Here's a working fork of your codepen.
(Incidentally, it looks like this examples slams into a known issue where line segments off the edge of the chart are dropped instead of clipping the segments. It won't be quite as bad if there are more points. I don't think I've found a good workaround. Hopefully we'll fix this soon.)

.append() to different selection depending on condition?

I have a similar issue as in Updating SVG Element Z-Index With D3
My preferred solution would be to have two groups within my svg as described in the answer by notan3xit.
But I have one data set, and a boolean flag on one of the data properties determines to which group the svg element belongs.
svg.selectAll("path")
.data(d)
.enter()
.if(d.property).appendToGroup1()
.else.appendToGroup2()
This obviously doesn't work, but something like this.
I only want to iterate through the data once, and during runtime append the generated svg elements to the according groups.
Any ideas how to go about achieving that with d3.js?
Try
.each(function(d){
var toAppend = $(this);
if (!!d.property){
toAppend.appendtoGroup1();
} else {
toAppend.appendToGroup2();
}
})//...
The general idea would be to pass d into a callback, and apply the right append method inside that callback.
It should be cleaner to first save reference to what you want to append to, in an enclosing scope so you can easily call .append() on it.
Try this:
var selectedPath=svg.selectAll("path").data(d);
svg.selectAll("path").each(function(d,i){
if(d3.select(this).attr("property's name"))
selectedPath.append("some data to group 1")
else
selectedPath.append("some data to group 2")
});
If I had your code better able to help.

Calling a function once when data enters in d3

Is there a way to call a function when data enters using data().enter()?
For example, in this current jsfiddle: http://jsfiddle.net/p3m8A/4/ , I have a function that draws a group and I want to call this function when new data enters. The current jsfiddle doesn't do anything but the objective is to click on the red square and using .data.enter draw a purple square when the red square is clicked.
The specific part I'm trying to get to work is :
canvas.selectAll("#boxGroup")
.data(data)
.enter().function(d,i) {
drawBox(150,20,d);
};
Thanks
The callback passed to the call method is actually passed the selection, not the data.
enterSelection.call(function(selection){/* this === selection */});
So, what you were probably looking for is the each method.
enterSelection.each(function(d, i){/* this is selection */ drawBoxes(150,20,d);});
You want the method .call(function(d))
This will run your function once, passing d as the array of all of the data you have provided. i is not defined for using call after enter().
If you want to draw multiple boxes, based on d, your code would look something like this:
canvas.selectAll("boxGroup")
.data(data)
.enter()
.call(function(d){drawBoxes(150,20,d);});
I've created a basic fiddle of this here.
Note that this is what you want to use if you want to call a function on the selection returned by .enter() in the same spot as you're using it. It's also possible to bind a function to the enter event of a given DOM element by using .on('enter',function), but this would require that the element that you are entering data into already exist.

changing d3.JSON in Hierarchical Bars to JSON.parse

I'm using http://bl.ocks.org/mbostock/1283663
and I'm trying to change the following code
d3.json("readme.json", function(root) {
hierarchy.nodes(root);
x.domain([0, root.value]).nice();
down(root, 0);
});
to a JSON.parse (some data). I don't have a problem pulling the JSON data but I am totally confused about what is being setup in the rest of the d3.json process with the hierarchy.node(root), x.domain and down(root)
You're probably going to have to read a little more documentation before having a working understanding of what's going in this function. This is a tricky example to start with; I'm just going to walk you through what my process of trying to understand would look like. Going line by line:
d3.json("readme.json", function(root) {
This loads the referenced json file and calls function with it. 'root' starts out equal to the json file.
hierarchy.nodes(root);
Looking through the code, we find where hierarchy is declared:
var hierarchy = d3.layout.partition()
.value(function(d) { return d.size; });
So hierarchy is some kind of layout and hierarchy.nodes will add some useful attributes to root that will make it easier to graph.
x.domain([0, root.value]).nice();
searching for "x" in the example we find x = d3.scale.linear().range([0, w]). Tt appears that x is a linear scale. Basically, the x function will transform values in the domain - [0, root.value] to the range [0, w]. w is the width of the svg. root.value is a little trickier. The node page says
value - the node value, as returned by the value accessor
But what is the value accessor? The initial declaration of indicates it has something todo with root's 'size' attribute, but what? At this point, the documentation starts to get pretty confusing so you might want to pop open the debugger and see exactly what the value attribute of root and root's children looks like.
down(root, 0);
The down function is unique to the example and well commented. Try reading through it while referencing the documentation and see if you can figure it out.

Resources