I am trying to use the click event in a line chart in d3. Everytime I click on a line I want the line to change color to red and display the country name. The problem I am having is when click a line it changes color however it reverts back to the original color. The country name appears correctly.
This is the code to generate the line:
let line = d3.select("#linechart")
.append("svg")
.attr("width", widthline + marginline.left + marginline.right)
.attr("height", heightline + marginline.top + marginline.bottom)
.append("g")
.attr("transform", "translate(" + marginline.left + "," + marginline.top + ")")
.attr("id", "diagram");
line.selectAll(".line").data(databyCountry).enter()
.append("path")
.attr("fill", "none")
.attr("stroke-width", .5)
.attr("class", "line")
.attr("d", d => lines(d[1]))
.style("stroke", "steelblue")
.on("mouseover", countryOver)
.on("mouseout", countryOut)
.on("click", click);
this is the event code:
function countryOver(event, d){
d3.select(this)
.style("stroke", "black")
.style("stroke-width", 2);
line.append("text")
.text(d[0])
.attr("class", "title-text")
.attr("x", x(d.date) )
.attr("y", y(d.stringency_index))
.attr("font-size", "15px")
.attr("fill", "black")
.attr("id", "countryName");
};
function countryOut(event, d) {
d3.select(this).style("stroke", "steelblue").style("stroke-width", .5);
d3.select("#countryName").remove();
};
function click(event, d) {
d3.select(this).style("stroke", "red").style("stroke-width", 2);
line.append("text")
.text(d[0])
.attr("class", "title-text")
.attr("x", 500)
.attr("y", 10)
.attr("font-size", "15px")
.attr("fill", "black")
.attr("id", "countryClick");
};
Hope someone could help me! Thanks!
Related
I'm trying to make a d3 tooltip that can show near my cursor when I hover over a dot. I tried lots of methods available online, but none of them works. The tooltip doesn't show, and there is no error messages in console. Could anyone help me with this? My code is as follows:
I suspect there might be some issue with svg, but not sure how to solve it.
var svg = d3.select("#linechart").append("svg").attr('id','cases')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xScale = d3.scaleTime()
.domain(d3.extent(getCovidDate(data,state), function(d){return new Date(d)}))
.range([0, width]);
var yScale = d3.scaleLinear()
.domain(d3.extent(getCases(data,state), function(d){return d}))
.range([height, 0]);
var line = d3.line()
.x(function(d) { return xScale(new Date(d[0])); }) // set the x values for the line generator
.y(function(d) { return yScale(d[1]); }) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line
var dataset = d3.zip(getCovidDate(data,state),getCases(data,state))
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
var div = d3.select("#cases").append("div")
.attr("class", "tooltip")
.style("display", "none");
svg.append("path")
.datum(dataset) // Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line) // Calls the line generator
.style('fill-opacity', 0)
.style('stroke','cadetblue')
.on('mouseover', function (d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '.55')})
.on('mouseout', function (d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '1')});
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle")
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d) { return xScale(new Date(d[0])) })
.attr("cy", function(d) { return yScale(d[1]) })
.attr("r", 5)
.style("fill",'cadetblue')
.on('mouseover', function (d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '.55')
div.style("display", "inline");
})
.on('mousemove',function(d){
div.text(d[1])
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 15) + "px")
})
.on('mouseout', function (d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '1')
div.style("display", "none");
});
svg.append("line")
.attr("x1", xScale(new Date(inputValue)))
.attr("y1", 0)
.attr("x2", xScale(new Date(inputValue)))
.attr("y2", height)
.style("stroke-width", 2)
.style("stroke", "red")
.style("fill", "none");
d3.select('#cases').remove()
I have the following code:
svg
.selectAll('mySlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arcGenerator)
.attr('fill', function(d){ return(color(d.data.key)) })
.attr("stroke", "black")
.style("stroke-width", "2px")
.style("opacity", 0.7)
And I'd like to append text elements right after each of my path elements.
This doesn't seem to work:
svg
.selectAll('mySlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arcGenerator)
.attr('fill', function(d){ return(color(d.data.key)) })
.attr("stroke", "black")
.style("stroke-width", "2px")
.style("opacity", 0.7)
.append('text')
.attr('class', 'label')
.text(function(d){ return d.data.key})
.attr("transform", function(d) { return "translate(" + arcGenerator.centroid(d) + ")"; })
.style("text-anchor", "middle")
.style("font-size", 10)
What's important to me is that each text element immediately follow the respective path element in the document so I can use the + CSS Sibling selector to show text on hover of the path element.
I'm open to alternatives, but can't think of any.
Ultimately I just want to display the text on hover for each pie segment.
Hi you can keep track of the selection storing it as a variable. The difference is that d3 working chaining and applying functions. You also need to add the class name for your selection, in case you want to select them.
const slices = svg
.selectAll('.mySlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arcGenerator)
.attr('fill', function(d){ return(color(d.data.key)) })
.attr("stroke", "black")
.style("stroke-width", "2px")
.style("opacity", 0.7)
.attr("class", "mySlices");
slices.append('text')
.attr('class', 'label')
.text(function(d){ return d.data.key})
.attr("transform", function(d) { return "translate(" + arcGenerator.centroid(d) + ")"; })
.style("text-anchor", "middle")
.style("font-size", 10);
I have a D3 v4 force simulation with several nodes. Each node has a group. When I mouse over one of the elements of that group(an invisible circle) I want one of the other elements (the red circle on that specific node only which I gave an id of "backcircle") to do something. Currently this is what I have, but it does it to all nodes not just the one I'm hovering over's element.
this.node = this.d3Graph.selectAll(null)
.data(this.props.nodes)
.enter()
.append("g")
.attr("class", "nodes");
this.node.append("circle")
.attr("id", "backCircle")
.attr("r", 60)
.attr("fill", "red")
this.node.append("svg:image")
.attr("xlink:href", function(d) { return d.img })
.attr("height", 60)
.attr("width", 60)
.attr("x", -30)
.attr("y", -30)
this.node.append("circle")
.attr("r", 60)
.attr("fill", "transparent")
.on( 'mouseenter', function(d) {
d.r = 65;
this.node.select("#backCircle")
.transition()
.attr("r", 80);
}.bind(this))
Before anything else, two important tips:
Do not use "transparent" in an SVG.
IDs are unique. So, use classes instead (or select by the tag name)
Back to your question:
There are several ways of selecting the circle element based on a sibling circle element. The first one is going up the DOM and down again, using this.parentNode. The second one, if you know exactly the sequence of the siblings, is using previousSibling.
In the following demos, I have 3 elements per group: a circle, a text and a rectangle. Hovering over the rectangle will select the circle.
First, the option with this.parentNode. in your case:
d3.select(this.parentNode).select(".backCircle")
Hover over the squares:
var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d + ",75)"
});
g.append("circle")
.attr("class", "backCircle")
.attr("r", 40)
.attr("fill", "teal")
g.append("text")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text("FOO");
g.append("rect")
.attr("x", 20)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.style("fill", "firebrick")
.on("mouseenter", function() {
d3.select(this.parentNode).select(".backCircle")
.transition()
.attr("r", 50)
}).on("mouseleave", function() {
d3.select(this.parentNode).select(".backCircle")
.transition()
.attr("r", 40)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Then, the option with previousSibling (here, you don't even need to set a class). In your case:
d3.select(this.previousSibling.previousSibling)
Hover over the squares:
var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d + ",75)"
});
g.append("circle")
.attr("r", 40)
.attr("fill", "teal")
g.append("text")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text("FOO");
g.append("rect")
.attr("x", 20)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.style("fill", "firebrick")
.on("mouseenter", function() {
d3.select(this.previousSibling.previousSibling)
.transition()
.attr("r", 50)
}).on("mouseleave", function() {
d3.select(this.previousSibling.previousSibling)
.transition()
.attr("r", 40)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
PS: Have in mind that, since I'm not using an object, there is no need for bind(this) in my snippets.
I think you need to select the node that is firing the mouseenter event from within its handler.
this.node.append("circle")
.attr("r", 60)
.attr("fill", "transparent")
.on( 'mouseenter', function(d) {
var mouseenterNode = d3.select(this)
mouseenterNode.attr("r", 65);
mouseenterNode.select("#backCircle")
.transition()
.attr("r", 80);
}.bind(this))
I have a simple force layout that calculates nodes and links when certain buttons are clicked. The first time the nodes are calculated and displayed everything is correctly positioned. However, when nodes are recalculated after another click, the position of the circles I have appended to the nodes are way off yet the text I added remains in the right place. Here's my JS:
//Compute Nodes and Links
var data = this.computePreviewNodes($(e.currentTarget).data("id"), $(e.currentTarget).data("type"));
var canvas = d3.select(".body").append("svg")
.attr("width", width)
.attr("height", screen.height/2)
.append("g");
canvas.append("text")
.text(compObj.name)
.attr("text-anchor", "middle")
.attr("font-size", "2em")
.attr("x", width/2)
.attr("y", 40);
var force = d3.layout.force()
.nodes(data.nodes)
.links(data.links)
.gravity(.05)
.distance(100)
.charge(-10)
.size([width, screen.height/2]);
var links = canvas.selectAll(".links")
.data(data.links)
.enter().append("line")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "blue");
var nodes = canvas.selectAll(".nodes")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "nodes")
.call(force.drag);
nodes.append("circle")
.attr("cx", function(d) {return d.x;})
.attr("cy", function(d) {return d.y;})
.attr("r", 10)
.attr("fill", "green");
nodes.append("text")
.text(function(d) {return d.name})
.attr("text-anchor", "right")
.attr("font-size", "1.8em")
.attr("y", 5);
force.on("tick", function(e) {
nodes
.attr("transform", function(d, i){
return "translate(" + d.x + ", " + d.y + ")";
});
links
.attr("x1", function(d) {return d.source.x;})
.attr("y1", function(d) {return d.source.y;})
.attr("x2", function(d) {return d.target.x;})
.attr("y2", function(d) {return d.target.y;})
})
force.start();
My computePreviewNodes() function simply comes up with what nodes need to be displayed based on which button is clicked. My thoughts are that maybe I'm not updating my node positions correctly after the second rendering of my nodes. Any ideas?
Here's my canvas at the first click:
And here it is when I click/calculate my nodes once again:
actually I am working on D3js charts . I have implemented pagination in legends but in the end there are two legends which are repeating.
how should i fix this??
I have provided the fiddle for my current code here. jsfiddle link
Thanks in advance for any help
var data=[
{
"age":"<5",
"population":2704659
},
{
"age":"5-10",
"population":4499890
},
{
"age":"10-13",
"population":6736433
},
{
"age":"14-16",
"population":2159981
},
{
"age":"16-18",
"population":3853788
},
{
"age":"18-22",
"population":8848383
},
{
"age":"22-30",
"population":8384390
},
{
"age":"30-44",
"population":14106543
},
{
"age":"45-64",
"population":8819342
},
{
"age":"≥65",
"population":800000
}
]
var width = 1060,
height = 600,
radius = 175,
color = d3.scale.category10(),
legendNo=4, // number of legends to display at a time
legendCount=0; //To store number of legends
//creating svg element and appending to body
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); //transform it to center of body
//creating start and end angle for each arc
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
//creating the arcs based on pie layout
var arc = d3.svg.arc() //inner arc with color
.outerRadius(radius)
.innerRadius(radius-70);
//calculate the total to display in hole
var total=0;
data.forEach(function(d) {
d.population;
total +=parseInt(d.population);
legendCount++;
});
//creating svg element for center text
var center_group = svg.append("svg:g")
.attr("class", "center_group")
.attr("transform", "translate(" + (width/2) + "," + (height/2) + ")");
//selecting all inner arcs and appending data
var g = svg.selectAll("arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc")
//giving colour to each inner arc and execute onClick function
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); })
//display text in the inner arcs
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.style("font-size", "14px")
.text(function(d) { return d.data.age; });
count = 0;
var p=0;
var viewdata = data.slice(p,p+legendNo);
var hidedata;
var temp;
//selecting all legend elements
var legend = svg.selectAll(".legend")
.data(viewdata).enter()
.append("g").attr("class", "legend")
//.attr("width", )
.attr("id", function() {
return count++;
})
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
//appending coloured rectangles to legend
svg.selectAll("rect")
.data(viewdata)
.enter().append("rect")
.attr("x", width/2-150)
.attr("y",5)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
.attr("width", 23).attr("height", 23)
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.style("fill", function(d) {
return color(d.age);
});
var prev=svg.append("svg:text")
.attr("id","prev")
.attr("x", width-1260)
.attr("y",height-385)
.attr("dy", "2.90em")
.attr("dx", "1.75em")
//.attr("stroke", "black")
//.style("fill","white")
// .style("text-anchor", "middle")
.style("font-size", "20px")
//.attr("width", 45).attr("height", 25)
.text("<|")
.on("click",onPrevClick)
var next=svg.append("svg:text")
.attr("id","next")
.attr("x", width-950)
.attr("y",height-385)
.attr("dy", "2.90em")
.attr("dx", "1.75em")
.style("font-size", "20px")
//.attr("stroke", "black")
//.style("fill","white")
//.attr("width", 45).attr("height", 25)
.text("|>")
.on("click",onNextClick)
function onNextClick()
{p+=legendNo;
if(p>=legendCount){
p-=legendNo;
viewdata = data.slice(p,legendCount);
//temp=legendNo-(legendCount-p);
//hidedata =data.slice(p-temp-2,p-2);
}
else{
viewdata = data.slice(p,p+legendNo);
//hidedata =data.slice(0,0);
}
svg.selectAll("rect")
.data(viewdata)
.attr("x", width/2-150)
.attr("y",5)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
.attr("width", 23).attr("height", 23)
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.style("fill", function(d) {
return color(d.age);
});
legend.select("text").attr("x", width/2-150)
.data(viewdata)
.attr("y", 15)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
//.attr("transform", function(d, i) {return "rotate("+45*i+","+d.age+",200)";})
.attr("text-anchor", "middle").text(function(d) {
return d.age;
});
}
function onPrevClick(){
p-=legendNo;
if(p<=0){
p=0;
}
viewdata = data.slice(p,p+legendNo);
svg.selectAll("rect")
.data(viewdata)
.attr("x", width/2-150)
.attr("y",5)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
.attr("width", 23).attr("height", 23)
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.style("fill", function(d) {
return color(d.age);
});
legend.select("text").attr("x", width/2-150)
.data(viewdata)
.attr("y", 15)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
//.attr("transform", function(d, i) {return "rotate("+45*i+","+d.age+",200)";})
.attr("text-anchor", "middle").text(function(d) {
return d.age;
});
}
// giving text to legends
legend.append("text").attr("x", width/2-150)
.data(viewdata)
.attr("y", 15)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
//.attr("transform", function(d, i) {return "rotate("+45*i+","+d.age+",200)";})
.attr("text-anchor", "middle").text(function(d) {
return d.age;
});
//displaying legend title
var legendTitle = svg.append("svg:text")
.attr("x", -(width/2-200))
.attr("y", height/2-25)
.style("font-size", "14px")
.style("text-decoration", "underline")
.text("Age Group");
Working demo: http://jsfiddle.net/m6cx7/6/
You need to handle enter and exit of the legends on each Next and Prev click:
var rects = svg.selectAll("rect")
.data(viewdata);
rects.enter()
.append('rect');
rects.exit()
.remove();
rects
.attr("x", width/2-150)
.attr("y",5)
// ...
var legends = svg.selectAll(".legend")
.data(viewdata);
legends.enter()
.append('g')
.classed('legend', true)
.attr("id", function() {
return count++;
})
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.append('text');
legends.exit()
.remove();
legends
.select('text')
.attr('x', width/2-150)
// ...