D3.js - Donut chart click event with multiple rings - javascript

I have been trying to implement D3.js donut with multiple rings. But, the problem is with click event as it works fine with click on first ring but, show weird behavior while clicking on the second ring. Also it shows some weird problems with mousehover as well.
{"metaData":null,
"data":{graphDetails":[{"displayName":"MUW","name":"DEF","score":5},{"displayName":"ABC","name":"ABCD","score":15},{"displayName":"DEFA","name":"DEF","score":35}],"graphOneDetails":[{"displayName":"D1","name":"D1","score":11},{"displayName":"D2","name":"D2","score":25},{"displayName":"D3","name":"D3","score":22}]},"success":true}
//Define arc ranges
var arcText = d3.scale.ordinal().rangeRoundBands([0, width], .1, .3);
// Determine size of arcs
var arc = d3.svg.arc().innerRadius(radius - 75).outerRadius(radius - 25);
var arc_2= d3.svg.arc().innerRadius(radius - 25).outerRadius(radius);
//Create the donut pie chart layout
d3.layout.pie().value(function(d){
return d["score"];
}).sort(null);
//Append SVG attributes and append g to the SVG
d3.select("#donut-chart")
.attr("width", width)
.attr("height",height)
.append("g")
.attr("transform","translate("+radius+","+radius+")");
//Define Inner Circle
svg.append("circle")
.attr("cx",0)
.attr("cy",0)
.attr("r",280)
.attr("fill","#fff");
//Calculate SVG paths and fill in the colors
var div = d3.select("body")
.append("div")
.attr("class","tooltip")
.style("opactiy",0);
// Append First Chart
var g = svg.selectAll(".arc").data(pie($scope.categories))
.enter()
.append("g")
.attr("class","arc")
.on("click",function(d, i){
alert(d.data.name)
}).on("mouseover",function(d){
alert(d.data.name);
}).on("mouseout",function(d){
alert(d.data.name);
});
g.append("path")
.attr("d",arc)
.attr("fill","#024");
g.append("text")
.attr("transform", function(d){
return "translate("+arc.centroid(d)+")";
}).attr("dy",".35em")
.style("text-anchor","middle")
.attr("fill","#fff")
.text(function (d,i){
return d.data.displayName
});
g.selectAll(".arc text").call(wrap.arcText.rangeBand());
//Append Second Chart
g.data(pie($scope.divisions)).append("path")
.attr("d",arc_2)
.attr("fill","#eee");
g.on("click, function(d,i){
alert(d.data.name);
}).on("mouseover", function(d){
alert(d.data.name);
});
//Append text to second chart
g.append("text")
.attr("transform", function(d){
return "translate("+arc_2.centroid(d)+")";
}).attr("dy",".35em")
.style("text-anchor","middle")
.attr("fill","#fff")
.text(function (d,i){
return d.data.displayName
});
g.selectAll(".arc text").call(wrap.arcText.rangeBand());
In initial state it works fine, but, when I click one chart it displays the data correctly. And when I click inner chart and updates my json to
{"metaData":null,
"data":{graphDetails":[{"displayName":"MUW","name":"DEF","score":5},{"displayName":"DEFA","name":"DEF","score":35}],"graphOneDetails":[{"displayName":"D1","name":"D1","score":11},{"displayName":"D3","name":"D3","score":22}]},"success":true}
Then it display inner chart as a full donut but, the outer chart comes as an arc instead of full donut. Same problem is happening with the mouse over as while I am hovering over the second chart each and everything comes correctly as a tool-tip. (I didn't include the code of tool-tip). But, I mouse over on ABC and returns me DEFA. So, I think there must be something related to the way I have appended these two arcs.
EDIT 1
Created the JSFidle, with my dataset and it's not showing anything
http://jsfiddle.net/pcr3ogt4/

Related

d3 mouseout not fired in nested group

I've built a graph in D3 where nodes can have multiple colors (e.g. 120° of the node is blue, another 120° slice is green and the remaining 120° yellow).
So the node is basically no longer a SVG circle element anymore but a pie chart that is based on path elements.
For interactivity reasons I increase the pie chart's radius if the user hovers over it. In order to scale it back, I'd like to listen to the mouseout event. My problem is that my event listener does not fire on mouseout. However it does fire on mouseover:
let node = this.nodeLayer.selectAll('.node')
.data(data, (n) => n.id);
node.exit().remove();
let nodeEnter = node.enter()
.append('g')
.attr('id', (n) => `calls function that generates a unique ID`)
.on('mouseover', this.nodeMouseOverHandler)
.on('mouseout', this.nodeMouseOutHandler)
If I only want to display, one color, I render a circle. If I want to display more than 1 color, I render a pie chart
nodeEnter.merge(node).each((d, i, nodes) => {
// call a function that returns a radius depending on whether the mouse pointer is currently hovering over the node
const radius = ...
// if I want to render one color
nodeEnter.append('circle')
//... customize
// if I want to render multiple colors
const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
const pie = d3.pie()
.value((d) => 1)
.sort(null);
// dummy colors
const colors = ['#ff0000', '#00ff00];
enter.append('g')
.selectAll('path')
.data(pie(colors))
.enter()
.append('path')
.attr('shape-rendering', 'auto')
.attr('d', arc)
.attr('fill', (d, i) => {
return colors[i];
})
.on('mouseout', (d, i, elems) => {
console.log('mouseout in subgroup');
this.nodeMouseOutHandler(d, elems[i]);
});
});
For the pie chart, the nodeMouseOverHandler fires perfectly fine, however the nodeMouseOutHandler doesn't fire at all. If I render a circle instead of the pie chart, everything works perfectly fine. Any ideas why I cannot observe the mouseout event?

D3 Adding dots to dynamic line chart

I have a scrolling line chart that is being updated in realtime as data comes in. Here is the js fiddle for the line chart. https://jsfiddle.net/eLu98a6L/
What I would like to do is replace the line with a dot graph, so each point that comes in would create a dot, and the scrolling feature is maintained.This is the type of chart I would like to create dow jones dot graph and ultimately I would like to remove the line underneath.
This is the code I have used to try and add dots to my graph.
g.append("g")
.selectAll("dot")
.data(4)
.enter("circle")
.attr("cx", "2")
.attr("cy", "2");
So far I haven't had any success. I'm very new to d3, so any help is appreciated. Thanks!
Based on your code an approach for this can be :
var circleArea = g.append('g'); /* added this to hold all dots in a group */
function tick() {
// Push a new data point onto the back.
data.push(random());
// Redraw the line.
/* hide the line as you don't need it any more
d3.select(this)
.attr("d", line)
.attr("transform", null);
*/
circleArea.selectAll('circle').remove(); // this makes sure your out of box dots will be remove.
/* this add dots based on data */
circleArea.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('r',3) // radius of dots
.attr('fill','black') // color of dots
.attr('transform',function(d,i){ return 'translate('+x(i)+','+y(d)+')';});
/* here we can animate dots to translate to left for 500ms */
circleArea.selectAll('circle').transition().duration(500)
.ease(d3.easeLinear)
.attr('transform',function(d,i){
if(x(i )<=0) { // this makes sure dots are remove on x=0
d3.select(this).remove();
}
return 'translate('+x(i-1)+','+y(d)+')';
});
/* here is rest of your code */
// Slide it to the left.
d3.active(this)
.attr("transform", "translate(" + x(-1) + ",0)")
.transition()
.on("start", tick);
// Pop the old data point off the front.
data.shift();
}
See it in action : https://codepen.io/FaridNaderi/pen/weERBj
hope it helps :)

d3 + drawing svgs + incorrectly overwriting

this i my jsfiddle that shows 2 piecharts which gives me something like this in the html view:
<svg width="220" height="220">
<svg width="220" height="220">
this is my fiddle where I am trying to insert an svg before the 2 piecharts, but I am not writing it correctly, 1 of the pie charts gets over written.
Can anyone advise how I can have all 3 svgs showing?
All my code as it is in the 2nd fiddle
var width = $(document).width()-50 || 960,
height = $(document).height()-50 ||500;
//attaches/appends a svg tag to a body tag with a width and height
/* COMMENT IN */
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
data=[[10, 5],[32, 24]]
z = d3.scale.category20c();
//attaches/appends a svg tag to a body tag with a width and height
var svg2 = d3.select("body").selectAll("svg")
.data(data)
.enter().append("svg:svg")
.attr("width", 220)
.attr("height", 220)
.append("svg:g")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
svg2.selectAll("path")
.data(d3.layout.pie())
.enter().append("svg:path")
.attr("d", d3.svg.arc()
.innerRadius(10)
.outerRadius(100))
.style("fill", function(d, i) { return z(i); });
NOTE: Some further background is that the first svg is a map that I have working. And What I am trying to do is create a layer on top of this which will be piecharts, in this case just 2, but you have to start somewhere :)
Your interpretation of the results is slightly wrong. The first pie chart doesn't get overwritten, it won't get rendered in the first place. In the commented in version you are appending the svg for your map, which works fine and can easily be identified by the width and height values assigned to it.
When trying to append the SVGs for your pie charts, you are selecting all SVGs in the body by doing d3.select("body").selectAll("svg"). This selection will include the previously appended SVG. You are then binding your data to the selection which will compute the data join. Since you are binding an array of data without any key function the join will be based on the index. Thus, the existing SVG will correspond to the first element in the data array and will be put to the update selection. As your code only works on the enter selection the first pie chart will never be drawn because it doesn't belong to this selection. The second one is output as expected.
One way to work around this is to use a CSS marker class for the pie charts. That way you could limit the second selection to the SVGs having the marker class:
var svg2 = d3.select("body").selectAll("svg.pie") // select with class "pie"
.data(data)
.enter().append("svg:svg")
.attr("width", 220)
.attr("height", 220)
.classed("pie", true) // set marker class for all pie charts
.append("svg:g")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
Because no SVG is present having class pie, all your data will end up in the enter selection rendering an SVG containing a pie chart for every data element as expected. I have updated your JSFiddle to show the effect.

In D3 How to enable mouse over event for current particular path?

I was creating a world map using d3.js.In that map i need to bind the mouseover event for every country.
For example: If i mouseover the india i need to change the Fill(Background) color for india only.
I implemented the mouseover event.But my problem is whenever i mouseover over the country(India) that function effecting all the countries.I mean fill color effecting all the countries.But it need to effect only current country.
I tried using this also but no luck for me.
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
Please help any one to solve my problem.
My Full Code
var width = 1000,
height = 500;
var projection = d3.geo.robinson()
.scale(150)
//.translate(100,100)
.precision(.5);
var path = d3.geo.path()
// .attr("class","path")
.projection(projection);
var svg = d3.select('#'+id)
.append('svg')
.attr("width", width)
.attr("height", height)
.attr("style", "background:" + json.bc);
//shape
d3.json("world.json", function(error, world) {
svg
.datum(topojson.feature(world, world.objects.countries))
.append("path")
.on("mouseover", function(){d3.select(this).style("fill", "red");})
.on("mouseout", function(){d3.select(this).style("fill", "white");})
.attr("style", "fill:" + json.cbc)
.attr("class", "country")
.attr("d", path)
;
});
Before mouseover
After MouseOver
This code:
svg
.datum(topojson.feature(world, world.objects.countries))
.append("path")
...
says --> I have one piece of data, draw me a path from it.
Change it up to this:
svg.selectAll(".countries")
.data(topojson.feature(world, world.objects.countries).features)
.enter()
.append("path")
...
which says --> I have multiple data (features), bind the data to my selection (selectAll) and draw me a path for each component.
Example here.

Adding a span icon to a D3 bar chart

I'm working on a fairly basic bar chart where I'm trying to have a span icon that appears, anchored at the start of each bar. Which icon appears is dependent on the class of the bar. For example, if the bar is blue, I want a certain icon vs. if the bar is red.
I've appended and added the span which shows up in the console, but is not actually appearing any where in the chart.
I have the icons stored as spans in my css, one for each version of the value name that gets plugged in.
I've tried a variety of selections, ordering, etc. But can't get it to stick.
var bars = svg.selectAll('.bar')
.data(data)
.enter().append('g')
.attr('class', 'bar');
bars.append('rect')
var icons = svg.selectAll('rect')
.data(data)
.enter().append("span")
.attr("class", function(d, i) {
return "icon-" + d.value + "-right";
})
.attr('dx', -6)
.attr('dy', (bar_height / 2) +5)
.attr('text-anchor', 'start');
You should use foreignObject element to insert HTML into SVG.
Like so:
var icons = svg.selectAll('foreignObject').data(data);
icons.enter().append("foreignObject")
.attr("class", function(d) { return "icon-" + d.value + "-right"; })
.append("xhtml:body")
.append("xhtml:span");
Also you can use text element to add icons to the SVG:
var icons = svg.selectAll('text').data(data);
icons.enter().append("text")
.html("&#xf00d") // utf-8 character for the icon

Categories

Resources