D3 circle element remains when mouse over on data update - d3.js

I have a D3 timeseries chart made up of line path and at each data point i use a circle which is appended to the lines. The circles have a mouse enter event attached to it and when the mouse is moved over the circle it displays the tooltip with the information about that data point and i also change the class of the circle so that it looks highlighted.
The problem i have got is, when the mouse is over the circle and the circle is highlighted and the tooltip is showing, at the same time if i get some new data and the chart is updated, the circle my mouse is over does not disappear even when the mouse is removed off the circle and it shows that circle hanging in the middle without being attached to any line.
I have attached the image of the chart showing the problem.
Any help to fix this problem will be highly appreciated.
Image showing d3 issue
Here's the jsfiddle code showing the issue. Try pointing your mouse to the circle and wait for the chart to update every 5 seconds

(Moving this from the comment section)
Take a look at this: https://jsfiddle.net/xvLgq8mn/
In the updateChart function you select by the circle class:
// update the circles at each data points
svg.selectAll('.circle') // here you correctly select all with the circle class
.data(this.props.data)
.attr('class', 'circle all')
.transition()
.duration(500)
.attr('cx', (d) => { return this.axis.x(d.time);})
.attr('cy', (d) => { return this.axis.y(d.count);});
but here, on mouseover, you remove the circle class and replace it with circle--highlight:
group.selectAll()
.data(this.props.data)
.enter().append('circle')
.attr('class', 'circle all')
.attr('cx', (d) => { return this.axis.x(d.time);})
.attr('cy', (d) => { return this.axis.y(d.count);})
.attr('r', 4)
.on('mousemove', function(d) {
// START: Show tooltip
div.transition()
.duration(1000)
.style('opacity', 1);
div.html('<div class="date--time">'
+ d.time
+ '</div><div class="count">' + d.count + ' incidents</div>')
.style('left', (d3.event.pageX) + 'px')
.style('top', (d3.event.pageY - 70) + 'px');
d3.select(this)
.attr('r', 6)
.attr('class', 'circle--highlight'); // here you change the class from circle all
// to just circle--highlight,
// so when you are hovering a circle and the chart changes,
// the circle you have hovered won't be updated because
// it won't be selected due to the class difference
// END: Show tooltip
})
.on('mouseleave', function() {
// hide tooltip and return the circles to original style
div.transition()
.duration(500)
.style('opacity', 0);
// set the circle back to normal
d3.select(this)
.attr('r', 4)
.attr('class', 'circle all');
});
So a solution would be to also add the circle class along with the circle--highlight like this:
d3.select(this)
.attr('r', 6)
.attr('class', 'circle circle--highlight');
Or change your select in the updateChart like this:
svg.selectAll('circle')
but that would need many more adjustments to your script in order for it to work as expected.
Hope this helps! Good luck!

Related

On hover append circle on top of hovered circle in D3 v5

I am trying to append a circle on mouse hover on the existing circle in D3 but not sure how to achieve the coordinates of a hovered circle and append new circle on top of it and remove it on mouse out.
mouse hover on green circle should display blue circle around it.
In short, put the green circle in a g element and attach listener to that element, which adds and removes the outer circle on mouseenter and mouseleave respectively.
I've thrown together a JSFiddle demoing it: https://jsfiddle.net/df23r1yj/
First append a g element for each data element (setting pointer-events to all makes it trigger events even when elements have no fill):
const circleG = svg.append('g')
.selectAll('g')
.data(data).enter()
.append('g')
.style('pointer-events', 'all')
Add a green circle for each data element:
circleG.append('circle')
.classed('persistentCircle', true)
.attr('cx', (d) => { return d.x })
.attr('cy', (d) => { return d.y })
.attr('r', (d) => { return d.r })
.style('stroke', 'green')
.style('fill', 'none')
.style('fill', 'black')
.style('fill-opacity', 0)
Adding event listeners that append and remove the outer blue circle. Using insert instead of the conventional append puts the outer circle behind the inner circle. Giving the outer circle a class - removeableCircle - makes it easy to remove it on mouseleave.:
circleG
.on('mouseenter', function () {
d3.select(this)
.insert('circle', ':first-child')
.classed('removeableCircle', true)
.attr('cx', (d) => { return d.x })
.attr('cy', (d) => { return d.y })
.attr('r', (d) => { return d.r * 1.5 })
.style('stroke', 'blue')
.style('fill', 'none')
.style('pointer-events', 'none')
})
.on('mouseleave', function () {
d3.select(this).selectAll('.removeableCircle').remove()
})
Hope this helps!

How do I add a transition delay between multiple individual transitioning polygons in D3?

The original Code can be found at: http://bl.ocks.org/Guerino1/3a51eeb95d3a8345bc27370e8c9d5b77
I have numerous polygons that are transitioning onto an svg canvas (from left to right, at the bottom of the HTML page).
The code I use to create an transition the chevrons leverages D3 Polygon:
// Create Polygons for SDLC
svgCanvas.selectAll("a")
.data(dataSet)
.enter().append("a")
.attr("xlink:href", function(d) { return d.link; })
.append("svg:polygon")
//svgCanvas.selectAll("polygon")
//.data(dataSet)
//.enter().append("polygon")
.attr("id", function(d,i){ return (selectString.replace(/ /g,'_').replace(/#/g,'') + "_index_" + i); })
.attr("originalcolor","violet")
.style("stroke","blue")
.style("fill","violet")
.style("stroke-width",2)
.attr("points", origin)
.on('mouseover', chevronMouseOver)
.on("mouseout", chevronMouseOut)
.on("click", chevronMouseOut)
.transition() // <------- TRANSITION STARTS HERE --------
.duration(3000)
.attr("points", calculateChevron);
Currently, all polygons transition into the svg canvas, together. I'd like to put a delay between each of them, so that it looks more like dealing from a deck of cards, one at a time.
How would I properly add a D3 delay to make this happen?
Thanks for any help you can offer.
try this..
.transition() // <------- TRANSITION STARTS HERE --------
.delay(function(d,i){ return 100*i; })
.duration(3000)
.attr("points", calculateChevron);

How do I properly add transitions to D3 Polygons?

Original Code can be found at: http://bl.ocks.org/Guerino1/451f4c47842967dd813c8a64b24f7686
Problem: Applying .transition() code to different polygon sets appears to yield different results.
The following portion of the code seems to work as expected. Applying a transition causes the chevrons to transition onto the svg canvas, from left to right...
svgChevronsCanvas.selectAll("a")
.data(dataSet)
.enter().append("a")
.attr("xlink:href", function(d) { return d.link; })
.append("svg:polygon")
.attr("id", function(d,i){ return ("chevron_" + selectString.replace(/ /g,'_').replace(/#/g,'') + "_index_" + i); })
.attr("originalcolor", polygonPrimaryColor)
//.style("stroke","blue")
//.style("stroke-width",1)
.style("fill", polygonPrimaryColor)
.attr("points", chevronOrigin)
.on('mouseover', chevronMouseOver)
.on("mouseout", chevronMouseOut)
.on("click", chevronMouseOut)
.transition() // <------------------- TRANSITION HERE
.duration(3000)
.attr("points", calculateChevron);
The following code, which attempts to follow the same pattern as above does not seem to work as expected. Given the transition, I would expect the textboxes (in light blue below the chevrons), which are also drawn using D3 polygons, to transition onto their svg canvas from left to right, just like the chevron polygons in the above code...
svgTextboxesCanvas.selectAll("a")
.data(dataSet)
.enter().append("a")
.attr("xlink:href", function(d) { return d.link; })
.append("svg:polygon")
.attr("id", function(d,i){ return ("textbox_" + selectString.replace(/ /g,'_').replace(/#/g,'') + "_index_" + i); })
.attr("originalcolor", textboxColor)
.style("stroke", textboxColor)
.style("stroke-width",1)
.style("fill", textboxColor)
.attr("points", textboxesOrigin)
.on('mouseover', textboxMouseOver)
.on("mouseout", textboxMouseOut)
.on("click", textboxMouseOut)
.transition()
.duration(3000)
.attr("points", calculateTextbox);
Question: How do I properly add transitions to the D3 polygons that are built to look like rectangles (below the chevrons), in the latter set of code, and make them transition into the page just like the dark blue chevrons above them?
In the original code:
Make
var chevronGapSpace = 5;//this is the distance between each rectangle.
var slantDepth = 0;//to make the polygon rectangle.
Next, to make rectangle transition inside function calculateChevron change the calculations accordingly:
function calculateChevron(d, i){
return [
[(svgMargin) + i*(chevronDistance),chevronTopOffset],
[(svgMargin + chevronWidth - slantDepth) + i*(chevronDistance),chevronTop],
[(svgMargin + chevronWidth - slantDepth) + i*(chevronDistance),chevronPointY],
[(svgMargin + chevronWidth - slantDepth) + i*(chevronDistance),chevronBottom],
[(svgMargin) + i*(chevronDistance),chevronBottom],
[(svgMargin + slantDepth) + i*(chevronDistance),chevronPointY]
];
};
working code here

D3.js: circle color change on title mouseover doesn't work

I'm appending some text to D3.js circles and want the circles to change color mouseover, also on mouseover on the text.
Currently the circles do change color on mouseover, but when hovering over the text, the circle mouseover doesn't work anymore (logical: I'm hovering over the text). How do I get the circles to also change color when hovering over the text?
My code (gnode is a earlier defined circle):
var label = gnode.append("text")
.text(function(d) { return d.key ; })
.attr("font-size", function(d) {return 12 + d.value[0]/4})
.attr("text-anchor", "middle")
.call(force.drag)
.on("mouseover", function(d){
this.style.cursor='pointer';
d3.select( "#" + d.key.toLowerCase().replace(/ /g, '_'))
.attr("id", "none")
.classed("mouse_over",true)
.classed("mouse_out",false);
thanks
You can achieve this by simply putting all the elements belonging together inside a group. Then attach the mouse events to the group instead of the elements themselves.
First create svg element and append data:
var svg = d3.select("#main")
.append("svg")
.attr("id", "svgElement")
.attr("width", width)
.attr("height", height);
var svgg = svg.selectAll("g.myGroup")
.data(myData)
.enter()
.append("g");
Then add your elements via the each function:
svgg.each(function (d, i) {
selection = d3.select(this);
// ... append to this selection
});
Now attach mouse events to the group:
svgg.on("mouseover", function(d) {
d3.select(this) // Select and do something
});
You can find the working fiddle here:
http://jsfiddle.net/77XLD/1/
Note that the event fires when either moving over the line of the circle and also when hovering over the text.

Why is the brush preventing dc.js barChart toolTips to appear?

I don't see why that behaviour was implemented.
Any good reason ?
In order to have a brushing function, a transparent rectangle that captures all mouse events has to be drawn over top of the graph. That prevents any mouse events from triggering the tooltip event handler on the main graph elements, and is the reason the dc.js API warns that leaving brushing behaviour "on" will disable all other interactive behaviour.
If you want both behaviours, consider using a focus + context layout. That example uses plain d3, but you could recreate it with dc.js. Just have two different views of the same data, one with the brush and one with the tooltips or other interactivity.
You can use https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events to block 'mouseover' event for brush so that tooltips are enabled. Then on chart you can create a custom 'mousedown' event and pass it to brush to enable selection
d3.select('.chartContainer').on('mousedown', function(){
brush_elm = self.scrubberContent.select(".brush").node();
new_click_event = new Event('mousedown');
new_click_event.pageX = d3.event.pageX;
new_click_event.clientX = d3.event.clientX;
new_click_event.pageY = d3.event.pageY;
new_click_event.clientY = d3.event.clientY;
brush_elm.dispatchEvent(new_click_event);
});
I had a similar issue using d3 code. I realized that moving the tooltip event after the brush event fixed the problem. For me, it looked like this:
svg.append("g")
.attr("class", "brush")
.call(brush);
svg.selectAll('circle')
.data(humidity_data)
.enter()
.append('circle')
.attr('class', 'humidity_point')
.attr('cx', function(d) {
return x(d['date'])
})
.attr('cy', function(d) {
return y(d['Humidity'])
})
.attr('r', 4)
.attr('fill', '#428bca')
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html(d.Custody + '<br>' + d.City + ', ' + d.Country + '<br>' + d.Humidity + '%')
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(1500)
.style("opacity", 0);
});
This code allowed brushing, but retained the ability to hover over a circle element and see metadata.

Resources