d3 mouseout not fired in nested group - javascript

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?

Related

How to have an entity trigger a different entities mouseover event (D3)

I currently am working on a GeoJSON of the United States with a mouseover event on every state (for example, one thing that occurs is that it changes the border of the states to bright red to show which one is being highlighted).
Some states are fairly small, especially in New England. I was trying to think of a way to get around this and decided to implement additional buttons on the side that would have the same mouseover event tied to specific states that I have deemed were small.
My question is: how would I go about getting something like highlighting the states borders to happen when mousing over the additional buttons.
My current implementation of the original states themselves is below (I haven't begun work on the additional buttons):
.on("mouseover", function(d) {
d3.select(event.target).attr("stroke", "red").attr("stroke-width", "3");
displayData(d);
});
.on("mouseout", function(d) {
d3.select(event.target).attr("stroke", "black").attr("stroke-width", "1");
hideData(d);
});
displayData(d) and hideData(d) are used for a tooltip's display. As you can see, the way the mouseover works right now is by capturing the event.target. How would I replicate this for a separate button? Is it possible to somehow tie that button's event.target to the corresponding state?
Just use selection.dispatch, which:
Dispatches a custom event of the specified type to each selected element, in order.
Here is a basic example, hovering over the circles will call a given function. Click on the button to dispatch a "mouseover" to the second circle.
const svg = d3.select("svg");
const circles = svg.selectAll(null)
.data(["foo", "bar", "baz"])
.enter()
.append("circle")
.attr("id", (_, i) => `id${i}`)
.attr("cy", 70)
.attr("cx", (_, i) => 30 + 100 * i)
.attr("r", 30)
.style("fill", "teal")
.on("mouseover", (event, d) => {
console.log(`My name is ${d}`);
});
d3.select("button").on("click", () => d3.select("#id1").dispatch("mouseover"));
.as-console-wrapper { max-height: 15% !important;}
<script src="https://d3js.org/d3.v7.min.js"></script>
<button>Click me</button>
<svg></svg>

Optimize rendering a map w/ large data set

I'm currently rendering a US map along with every district's border. The grabbing the features of the topojson, we have an array of ~13,000 rows.
I'm also joining data to the topojson file, and running through a csv of ~180,000 rows. I believe I've optimized this process of joining data by ID enough using memoization, where the CSV is essentially turned into a hash, and each ID is the key to it's row data.
This process^ is run 24 times in Next JS through SSG to further the user experience, and so all 24 versions of this map is calculated before the first visit of this deployment. I'm sadly timing out during deployment phase for this specific web page^.
I've inspected the program and seem to find that painting/filling each district may be what's causing the slowdown. Are there any tips you all use to optimize rendering an SVG map of many path elements? Currently the attributes to this map:
1: tooltip for each district styled in tailwind
2: around 5 properties turned to text from topojson file w/ joined data to display upon hover, displayed by tooltip
3: filled dynamically with this snippet which runs a function based on the district's property type
.attr('fill', function (d) {return figureOutColor(d['properties'].type);})
4: adding a mouseover, mousemove, and mouseout event handler to each district.
Edit: Code snippet of adding attrs to my map
export const drawMap = (svgRef: SVGSVGElement, allDistricts: any[]) => {
const svg = d3.select(svgRef);
svg.selectAll('*').remove();
const projection = d3.geoAlbersUsa().scale(900).translate([400, 255]);
const path = d3.geoPath().projection(projection);
const tooltip = d3
.select('body')
.append('div')
.attr(
'class',
'absolute z-10 invisible bg-white',
);
svg
.selectAll('.district')
.data(allDistricts)
.enter()
.append('path')
.attr('class', 'district stroke-current stroke-0.5')
.attr('transform', 'translate(0' + margin.left + ',' + margin.top + ')')
.attr('d', path)
.attr('fill', function (d) {
return figureOutColor(d['properties'].type);
})
.on('mouseover', function (d) {
return tooltip
.style('visibility', 'visible')
.text(`${d.properties.name});
})
.on('mousemove', function (data) {
return tooltip.style('top', d3.event.pageY - 40 + 'px').style('left', d3.event.pageX + 'px');
})
.on('mouseout', function (d) {
d3.select(this).classed('selected fill-current text-white', false);
return tooltip.style('visibility', 'hidden');
});

Why is the hover state behaviour removed?

I have a D3 generated map which needs to be able to dynamically change the fill element of drawn paths. The original paths are generated and assigned a class of 'boundaries'. The hover behaviour is set to turn the country yellow when the user hovers the cursor over the country. However, if I then go and dynamically change the fill color of the country, for example by using d3.selectAll- (I have simplified the below example so that this behaviour is simulated by uncommenting the last section), the hover behaviour stops working. The class has not changed, so why is the hover behaviour now not occurring.. and is there a workaround for this?
CSS
.countryMap{
width: 500px;
height: 500px;
position: relative;
}
.boundaries{
fill:green;
}
.boundaries:hover{
fill:yellow;
}
Javascript
const countryMap = {};
const FILE = `aus.geojson`; // the file we will use
var svg = d3
.select('div.country__map')
.append('svg')
.attr('width',200)
.attr('height',200)
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr('viewBox','770 270 200 150')
d3.json(FILE).then(function (outline) {
countryMap.features = outline.features;
// choose a projection
const projection = d3.geoMercator();
// create a path generator function initialized with the projection
const geoPath = d3.geoPath().projection(projection);
drawOutline();
function drawOutline() {
svg
.selectAll('path.boundaries') // CSS styles defined above
.data(countryMap.features)
.enter()
.append('path')
.attr('class', 'boundaries')
.attr('d', geoPath)
// .style('fill', d => {
// return 'green';
// })
}
})
As #Michael mentioned it will be better to manually add or remove class using js.
D3 provides us mouseover and mouseout events which can be used to add and remove class.
Here on hover, we are applying the 'active' class on the element.
svg
.selectAll('path.boundaries')
.data(countryMap.features)
.enter()
.append('path')
.attr('class', 'boundaries')
.attr('d', geoPath)
.on('mouseover', function () {
d3.select(this).classed("active", true)
})
.on('mouseout', function () {
d3.select(this).classed("active", false)
})
We also need to update the CSS according to these changes.
You can update the CSS to:
.boundaries{
fill:green;
}
.boundaries.active{
fill:yellow;
}

D3.js - Donut chart click event with multiple rings

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/

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.

Categories

Resources