D3 - Center the globe to the clicked country - javascript

In the example below, I see that when a user selects a country, the globe rotates and centers to that country. How can I code it such that when a country is clicked, the globe centers to that country?
D3 Globe
I have tried to do add an on click handler before the mouseover event, but it doesn't seem to work. Any ideas?
.on("click", function(d) {
var rotate = projection.rotate(),
focusedCountry = country(countries, this),
p = d3.geo.centroid(focusedCountry);
svg.selectAll(".focused").classed("focused", focused = false);
//Globe rotating
(function transition() {
d3.transition()
.duration(2500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
svg.selectAll("path").attr("d", path)
.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
};
})
})();
})

First create a click to the path like this:
.on("click", function(d){rotateMe(d);})
Change code for the clicked data. Get the id of the clicked path and get its country data.
var rotateMe = function(d) {
var rotate = projection.rotate(),
focusedCountry = country(countries, d.id),//get the clicked country's details
p = d3.geo.centroid(focusedCountry);
console.log(focusedCountry, "hello")
svg.selectAll(".focused").classed("focused", focused = false);
//Globe rotating
(function transition() {
d3.transition()
.duration(2500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
svg.selectAll("path").attr("d", path)
.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
};
})
})();
};
working code here

Related

dc.js - Rendering two objects (one chart - renders, one shape - doesn't) together in one group?

I have two elements I need to render and a context of the big picture I am trying to achieve (a complete dashboard).
One is a chart that renders fine.
$scope.riskChart = new dc.pieChart('#risk-chart');
$scope.riskChart
.width(width)
.height(height)
.radius(Math.round(height/2.0))
.innerRadius(Math.round(height/4.0))
.dimension($scope.quarter)
.group($scope.quarterGroup)
.transitionDuration(250);
The other is a triangle, to be used for a more complex shape
$scope.openChart = d3.select("#risk-chart svg g")
.enter()
.attr("width", 55)
.attr("height", 55)
.append('path')
.attr("d", d3.symbol('triangle-up'))
.attr("transform", function(d) { return "translate(" + 100 + "," + 100 + ")"; })
.style("fill", fill);
On invocation of render functions, the dc.js render function is recognized and the chart is seen, but the d3.js render() function is not recognized.
How do I add this shape to my dc.js canvas (an svg element).
$scope.riskChart.render(); <--------------Works!
$scope.openChart.render(); <--------------Doesn't work (d3.js)!
How do I make this work?
EDIT:
I modified dc.js to include my custom chart, it is a work in progress.
dc.starChart = function(parent, fill) {
var _chart = {};
var _count = null, _category = null;
var _width, _height;
var _root = null, _svg = null, _g = null;
var _region;
var _minHeight = 20;
var _dispatch = d3.dispatch('jump');
_chart.count = function(count) {
if(!arguments.length)
return _count;
_count = count;
return _chart;
};
_chart.category = function(category) {
if(!arguments.length)
return _category
_category = category;
return _chart;
};
function count() {
return _count;
}
function category() {
return _category;
}
function y(height) {
return isNaN(height) ? 3 : _y(0) - _y(height);
}
_chart.redraw = function(fill) {
var color = fill;
var triangle = d3.symbol('triangle-up');
this._g.attr("width", 55)
.attr("height", 55)
.append('path')
.attr("d", triangle)
.attr("transform", function(d) { return "translate(" + 25 + "," + 25 + ")"; })
.style("fill", fill);
return _chart;
};
_chart.render = function() {
_g = _svg
.append('g');
_svg.on('click', function() {
if(_x)
_dispatch.jump(_x.invert(d3.mouse(this)[0]));
});
if (_root.select('svg'))
_chart.redraw();
else{
resetSvg();
generateSvg();
}
return _chart;
};
_chart.on = function(event, callback) {
_dispatch.on(event, callback);
return _chart;
};
_chart.width = function(w) {
if(!arguments.length)
return this._width;
this._width = w;
return _chart;
};
_chart.height = function(h) {
if(!arguments.length)
return this._height;
this._height = h;
return _chart;
};
_chart.select = function(s) {
return this._root.select(s);
};
_chart.selectAll = function(s) {
return this._root.selectAll(s);
};
function resetSvg() {
if (_root.select('svg'))
_chart.select('svg').remove();
generateSvg();
}
function generateSvg() {
this._svg = _root.append('svg')
.attr({width: _chart.width(),
height: _chart.height()});
}
_root = d3.select(parent);
return _chart;
}
I think I confused matters by talking about how to create a new chart, when really you just want to add a symbol to an existing chart.
In order to add things to an existing chart, the easiest thing to do is put an event handler on its pretransition or renderlet event. The pretransition event fires immediately once a chart is rendered or redrawn; the renderlet event fires after its animated transitions are complete.
Adapting your code to D3v4/5 and sticking it in a pretransition handler might look like this:
yearRingChart.on('pretransition', chart => {
let tri = chart.select('svg g') // 1
.selectAll('path.triangle') // 2
.data([0]); // 1
tri = tri.enter()
.append('path')
.attr('class', 'triangle')
.merge(tri);
tri
.attr("d", d3.symbol().type(d3.symbolTriangle).size(200))
.style("fill", 'darkgreen'); // 5
})
Some notes:
Use chart.select to select items within the chart. It's no different from using D3 directly, but it's a little safer. We select the containing <g> here, which is where we want to add the triangle.
Whether or not the triangle is already there, select it.
.data([0]) is a trick to add an element once, only if it doesn't exist - any array of size 1 will do
If there is no triangle, append one and merge it into the selection. Now tri will contain exactly one old or new triangle.
Define any attributes on the triangle, here using d3.symbol to define a triangle of area 200.
Example fiddle.
Because the triangle is not bound to any data array, .enter() should not be called.
Try this way:
$scope.openChart = d3.select("#risk-chart svg g")
.attr("width", 55)
.attr("height", 55)
.append('path')
.attr("d", d3.symbol('triangle-up'))
.attr("transform", function(d) { return "translate(" + 100 + "," + 100 + ")"; })
.style("fill", fill);

How to create target lines in a rowchart in dc.js

i'm using a rowchart to show the total of sales by item of a salesman.
Already tried a composite chart unsuccessfully like many posts from the google, but none of the examples uses a rowchart.
I need to do like the image, creating the red lines to represent the sale value target for each item, but i dont know how, can you guys help me? Thanks!
Actually this is my code to plot the rowchart
spenderRowChart = dc.rowChart("#chart-row-spenders");
spenderRowChart
.width(450).height(200)
.dimension(itemDim)
.group(totalItemGroup)
.elasticX(true);
Obviously you need a source for the target data, which could be a global map, or a field in your data.
I've created an example which pulls the data from a global, but it would also take from the data if your group reduction provides a field called target.
Then, it adds a new path element to each row. Conveniently the rows are already SVG g group elements, so anything put in there will already be offset to the top left corner of the row rect.
The only coordinate we are missing is the height of the rect, which we can get by reading it from one of the existing bars:
var height = chart.select('g.row rect').attr('height');
Then we select the gs and use the general update pattern to add a path.target to each one if it doesn't have one. We'll make it red, make it visible only if we have data for that row, and start it at X 0 so that it will animate from the left like the row rects do:
var target = chart.selectAll('g.row')
.selectAll('path.target').data(function(d) { return [d]; });
target = target.enter().append('path')
.attr('class', 'target')
.attr('stroke', 'red')
.attr('visibility', function(d) {
return (d.value.target !== undefined || _targets[d.key] !== undefined) ? 'visible' : 'hidden';
})
.attr('d', function(d) {
return 'M0,0 v' + height;
}).merge(target);
The final .merge(target) merges this selection into the main selection.
Now we can now animate all target lines into position:
target.transition().duration(chart.transitionDuration())
.attr('visibility', function(d) {
return (d.value.target !== undefined || _targets[d.key] !== undefined) ? 'visible' : 'hidden';
})
.attr('d', function(d) {
return 'M' + (chart.x()(d.value.target || _targets[d.key] || 0)+0.5) + ',0 v' + height;
});
The example doesn't show it, but this will also allow the targets to move dynamically if they change or the scale changes. Likewise targets may also become visible or invisible if data is added/removed.
thank you, due the long time to have an answer i've developed a solution already, but, really thank you and its so nice beacause its pretty much the same ideia, so i think its nice to share the code here too.
The difference its in my code i use other logic to clear the strokes and use the filter value of some other chart to make it dynamic.
.renderlet(function(chart) {
dc.events.trigger(function() {
filter1 = yearRingChart.filters();
filter2 = spenderRowChart.filters();
});
})
.on('pretransition', function(chart) {
if (aux_path.length > 0){
for (i = 0; i < aux_path.length; i++){
aux_path[i].remove();
}
};
aux_data = JSON.parse(JSON.stringify(data2));
aux_data = aux_data.filter(venda => filter1.indexOf(venda.Nome) > -1);
meta_subgrupo = [];
aux_data.forEach(function(o) {
var existing = meta_subgrupo.filter(function(i) { return i.SubGrupo === o.SubGrupo })[0];
if (!existing)
meta_subgrupo.push(o);
else
existing.Meta += o.Meta;
});
if (filter1.length > 0) {
for (i = 0; (i < Object.keys(subGrupos).length); i++){
var x_vert = meta_subgrupo[i].Meta;
var extra_data = [
{x: chart.x()(x_vert), y: 0},
{x: chart.x()(x_vert), y: chart.effectiveHeight()}
];
var line = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveLinear);
var chartBody = chart.select('g');
var path = chartBody.selectAll('path.extra').data([extra_data]);
path = path.enter()
.append('path')
.attr('class', 'oeExtra')
.attr('stroke', subGruposColors[i].Color)
.attr('id', 'ids')
.attr("stroke-width", 2)
.style("stroke-dasharray", ("10,3"))
.merge(path)
path.attr('d', line);
aux_path.push(path);
}
}
})
And that's how it looks

d3 radar chart -- radialLine creates path but without coordinates

This is probably a pretty specific question:
My problem is that in d3.js i need to create a radial chart.
I created the axis and labels.
Now i want to draw the radialLine.
It creates the path objects in my HTML document,
but without any coordinates.
I think it has something to do with the way the radius/data is provided to the radialLine, but can't figure out what to change...
Hopefully someone sees my mistake.
I also created a JSfiddle:
complete JSfiddle
//Data:
var notebookData = [{
model: "Levecchio 620RE",
data: [579, 8, 2.4, 256, 13.3]
}];
var categories = [
"Price",
"RAM",
"CPU",
"Storage",
"Display"
];
var priceScale = d3.scaleLinear().domain([2500,300]).range([0,100]);
var ramScale = d3.scaleLinear().domain([0,32]).range([0,100]);
var cpuScale = d3.scaleLinear().domain([1.0,3.2]).range([0,100]);
var storageScale = d3.scaleLinear().domain([64,2048]).range([0,100]);
var displaySizeScale = d3.scaleLinear().domain([10.0,20.0]).range([0,100]);
function selectScale(category_name) {
switch(category_name) {
case "Price":
return priceScale;
case "RAM":
return ramScale;
case "CPU":
return cpuScale;
case "Storage":
return storageScale;
case "Display":
return displaySizeScale;
}
}
var scaledData = notebookData.map(function (el) {
return el.data.map(function (el2, i) { //el = 1 notebook
return selectScale(categories[i])(el2);
});
});
//My RadialLine
//generatorfunction
var radarLine = d3.radialLine()
.radius(function(d) { return scaledData(d.value); })
.angle(function(d,i) { return i*angleSlice; })
.curve(d3.curveLinearClosed)
;
//Create the wrapper
var radarWrapper = g.selectAll(".radarWrapper")
.data(notebookData)
.enter().append("g")
.attr("class", "radarWrapper")
;
//Create pathlines
radarWrapper.append("path")
.attr("class", "radarStroke")
.attr("d", function(d,i) { return radarLine(d); })
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", function(d,i) { return cfg.color(i); })
.style("fill", "none")
;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I've edited your fiddle a bit to make it work:
https://jsfiddle.net/2qgygksL/75/
Basicly what i've done:
fix the color scheme
var colors = d3.scale.category10();
instead of
var colors = d3.scale.ordinal(d3.schemeCategory10);
added data to path
radarWrapper.append("path")
.data(scaledData)
change radius to
.radius(function(d, i) {
return d;
})
since You used something like return scaledData(d.value); where your scaledData is an array.

Select all DOM node of a class except one in javascript

I am working with D3.js and actually I am trying to select all elements with a certain class except one on the particular event "mouseover".
I tried different kind of solutions, but no one worked or just partially.
This is my first solution:
.on("mouseover",
function(d) {
d3.select(this)
.style("stroke-width", "4px");
var selectedMethod = document.getElementById(d.name + "-line");
var notSelectedMethods = $(".method").not(selectedMethod);
d3.selectAll(notSelectedMethods)
.style("opacity", 0.2);
var selectedLegend = document.getElementById(d.name + "-legend");
var notSelectedLegends = $(".legend").not(selectedLegend);
d3.selectAll(notSelectedLegends)
.style("opacity", 0.2);
}
)
Debugging I can see that notSelectedMethods store all nodes ignoring the not() function. This is true for the first part, because with the second one of the snippet work.
Looking around I found this one, so I tried what that they said (focusing on the first part, the line selection), but no one work.
d3.selectAll(".method")
.style("opacity",
function() {
return (this === selectedMethod) ? 1.0 : 0.2;
}
);
Or
var selectedMethod = this;
d3.selectAll(".method")
.filter(
function(n, i) {
return (this !== selectedMethod);
}
)
.style("opacity", 0.2);
Or
d3.selectAll(".method:not(." + this.id + ")")
.style("opacity", 0.2);
How can I solve this issue?
UPDATE:
#TomB and #altocumulus point me in the right direction. With some bit changes, code are now working.
var selectedMethod = d.name;
d3.selectAll(".method")
.style("opacity",
function(e) {
return (e.name === selectedMethod) ? 1.0 : 0.2;
}
);
I did not mention data structure of the d element, that's was my bad.
This snipper do the job. I think I cannot do better, am I right?
UPDATE 2:
I cheered too soon. I tried to replicate previously on mouseover solution on legend to change lines and legend (same logic as before)
.on("mouseover",
function(d) {
// Change opacity of legend's elements
var selectedLegend = d.name;
d3.selectAll(".legend")
.style("opacity",
function(e) {
return (e.name === selectedLegend) ? 1.0 : 0.2;
}
);
// Change width of selected line
var selectedMethod = d.name;
d3.selectAll(".method")
.style("stroke-width",
function(e) {
return (e.name === selectedMethod) ? "4.5px" : "1.5px";
}
);
// Change opacity of no-selected lines
d3.selectAll(".method")
.style("opacity",
function(e) {
return (e.name === selectedMethod) ? 1.0 : 0.2;
}
);
I do not know why, snippet where I change width do not work (width does not change).
You have to compare the id like that :
.on("mouseover", function(d) {
d3.select(this).style("stroke-width", "4px");
var selectedLine = d.name + "-line";
d3.selectAll(".method")
.style("opacity", function(e) {
return (e.id === selectedLine) ? 1.0 : 0.2;
});
})
All items with "method" class will have an opacity of 1 except the one with id === d.name + "-line"
You can't compare JQuery items and D3 items like that, have a look at http://collaboradev.com/2014/03/18/d3-and-jquery-interoperability/
#TomB and #altocumulus point me in the right direction. With some bit changes, code are now working.
var selectedMethod = d.name;
d3.selectAll(".method")
.style("opacity",
function(e) {
return (e.name === selectedMethod) ? 1.0 : 0.2;
}
);
I did not mention data structure of the d element, that's was my bad.

How to add tupsy popup to a stacked area chart - protovis

I have been trying to add a popup displaying the data type shown in each layer of my stacked area chart. Here is what I have done so far:
Basically when I mouse over either layer of the stacked area chart, im getting the same type displayed in the popup, I can see why this is, but I have no idea how to make it state one value when mouse is over that layer, and the other value when its over the second layer.
Please could anyone help?
/* y-axis scale is the largest data item scaled to height of the panel */
yScale = pv.Scale.linear(0, pv.max(data, function (d) {
var totalValue = 0;
for (i = 0; i < stackLayers.length; i++) {
totalValue = totalValue + d[stackLayers[i]];
}
return totalValue;
})).range(0, h),
colors = pv.Colors.category20().range();
/* render the area's using the predefined protovis stack layout */
vis.add(pv.Layout.Stack)
.layers(stackLayers)
.values(data)
.x(function (d) {
return xScale(d.x);
})
.y(function (d, p) {
return yScale(d[p]);
})
.layer.add(pv.Area)
.interpolate("cardinal")
.tension(0.97)
.fillStyle(function (d) {
return colors[this.parent.index];
})
.text(function (d) {
if(stackLayers[i]== "HAL")
{return "Dave";}
else
{return "Sorry";}
/* return (stackLayers[i]== "Hal" ? "Dave" : "Sorry");*/
})
.event("mouseover", pv.Behavior.tipsy({gravity:"s", fade:true}))
.anchor("top").add(pv.Line)
.strokeStyle(function (d) {
return colors[this.parent.index].darker();
})
.interpolate("cardinal")
.tension(0.97)
.lineWidth(2);
/* Y-axis rule. */
vis.add(pv.Rule)
.data(yScale.ticks(5))
.left(0)
.width(w)
.bottom(yScale)
.strokeStyle(function (d) {
return d ? "rgba(128,128,128,.2)" : "#000";
})
.anchor("left").add(pv.Label)
.text(yScale.tickFormat);

Categories

Resources