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

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>

Related

D3 - how to fire an on-click function for only a specific group of nodes

In this d3 force layout viz project I'm trying to remove the user's ability to click on the bigger yellow nodes. I have an on-click, fire the clicknodeControl function,
nodes.append('circle')
.attr("r", 28)
.attr("id", "hoverdots")
.style("opacity", 0)
.style("fill", "#00bedd")
.on("click", function(d) { return clicknodeControl(d.group); })
.style("z-index", "10")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
and then ideally this variable would pass through the clicknode function to one group of nodes but not the other:
var clicknodeControl = d3.scaleOrdinal([`clicknode`, ``]);
This does not seem to be working in practice, the clicknode function is not being passed through.
Thanks if anyone has any ideas on this!!
In D3 V7 mouse event handlers have event as the first argument and datum as the second.
Replace
.on("click", function(d) { return clicknodeControl(d.group); })
with:
.on("click", (_, d) => clicknodeControl(d.group))

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');
});

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?

Dynamically add rectangles on click event

I'm trying to add rectangles dynamically on-click event. The goal is once i click on a rectangle, additional rows with text will be added.
I have tried to use the script below:
svg.append("rect")
.attr("x", 0)
.attr("y", 31)
.attr("width", 250)
.attr("height", 30)
.attr("fill", "rgb(82,82,82)")
.attr("stroke-width", 0)
.on("click",function ()
{
MyDataSource= dataset.filter(function(d) {
return d.Topic== "MyTopic";
});
console.log("Button clicked");
var topics = svg.selectAll("g")
.data(MyDataSource)
.enter()
.append("g")
.attr("class","bar");
var rects = topics.append("rect")
.attr("y", function(d, i) {
return (i+2) * 31;
})
.attr("x", 0)
.attr("width", 250)
.attr("height",30)
.attr("fill", "rgb(8,81,156)")
.attr("stroke-width",0)
.append("title")
.text(function(d) {
return d.Topics;
});
var recttext = topics.append("text")
.attr("y", function(d,i) {
return (i+2)*31+20;
})
.attr("x", 150)
.text(function(d) {
return d.Topics;
}) ;
});
Once i click on that rectangle, the console would have "Button clicked" but nothing will happen on the page.
If i take away the script in the Onclick function and add it to the page default code, the rows would be added successfully when the page first loads.
How can I set this code to work correctly on the On-click event?
Thanks in advance
If you want to add new rectangles on every click, make sure you also add new data to MyDataSource. With the selection.data().enter().append() pattern, d3 compares the argument passed to data against whatever data is exists on selection. New elements are appended for all new data. Right now, the data is the same on every click (except the first), so d3 decides there is nothing to update. Thus, topics refers to an empty selection and your subsequent calls to append to it will not render anything.
However, if MyDataSource is not changing at all, you'd just end up with a long array that contains many copies of your data set. A better choice in this case is to use .datum() instead of .data(), which allows you to bind many elements to the same single datum. If that datum is an array, you can then iterate through it to create new elements and achieve the same end as .data() but a cleaner underlying data structure.

How to override the dragging events in C3.js

I am currently trying to implement a zooming feature that is slightly different from the one that already exists.
Actually, I would like that if a user clicks and drags on the graph it zooms on the so defined domain. I would like to do it that way because with the mouse wheel it prevents the user from the page up/down.
As it doesn't seem to be possible with the C3.js API, I tried to implement the drag event by following this little walkthrough on D3.js Drag Behaviour.
However, I didn't understand it well as it is not working when I try it on the graph.
Here's a sample of my code :
function setHandlers() {
/**
* A custom drag event to zoom on the graph
*/
var drag = d3.behavior.drag();
d3.selectAll('.c3-event-rects').on(".drag", null);
drag
.on('dragstart', function (d) {
d3.event.sourceEvent.stopPropagation();
console.log("start");
console.log(d)
})
.on('drag', function (d) {
console.log("on bouge :)")
})
.on('dragend', function (d) {
console.log("end");
console.log(d)
})
}
I call this function whenever I refresh my graph and I have already coded a custom handler for a double click (in the same function but I have it off to be more clear). I would like to know how to successfully trigger a drag event in a C3.js graph, especially the dragstart and dragend events?
c3-event-rects marks the layer that drives the events (tooltips, clicks, etc.). You don't usually want to move this unless you want the event reactions to be offset (like you get the bar 1 tooltips when you hover over someplace else than over bar 1)
Also, the layer has it's opacity set to 0, so you can't actually see it move unless you override it's opacity. Something like
.c3-event-rects {
fill: red;
fill-opacity: 0.2 !important;
}
That said, here is how you do this
var drag = d3.behavior.drag()
.origin(function () {
var t = d3.select(this);
var translate = d3.transform(t.attr('transform')).translate;
return { x: translate[0], y: translate[1]};
})
.on("drag", function () {
d3.select(this)
.attr("transform", "translate(" + d3.event.x + " " + d3.event.y + ")")
})
d3.selectAll('.c3-event-rects').call(drag);
Fiddle - http://jsfiddle.net/d5bhwwLh/
Here's how it looks - the red spot is where I had my mouse over to make the tooltip appear for the first set of bars

Categories

Resources