D3.js looping through multiple functions on hover
I used the tutorial to get this function on mouseover:
function arcTween(outerRadius, delay) {
return function () {
d3.select(this).transition().delay(delay).attrTween("d", function (d) {
var i = d3.interpolate(d.outerRadius, outerRadius);
return function (t) { d.outerRadius = i(t); return arc(d); };
});
};
}
And I add it to the parts of the pie chart like this:
.on("mouseover", arcTween(outerRadius, 0, 0))
However, I also have text tags added to the svg for each slice in the pie chart and want them to disappear if you hover over another slice. So I gave these id tags when I create them based on the index and then wrote these two methods:
function visibilityShow(dataSetSize) {
for (var i = 0; i < dataSetSize; i++) {
$("#" + i).show();
}
}
function visibilityHide(index, dataSetSize) {
for (var i = 0; i < dataSetSize; i++) {
if (i === index) {
$("#" + i).show();
} else {
$("#" + i).hide();
}
}
}
Now they work in a vacuum, but when I try to put them on a mouseover event it doesn't work. arcTween stops working and "i" is always 0. This is what I tried:
Adding another .on ("mouseover", ...)
.on("mouseover", arcTween(outerRadius, 0))
.on("mouseover", visibility(0, dataSet.length));
and also tried to loop through the index with:
.on("mouseover", arcTween(outerRadius, 0))
.on("mouseover", function (d, i) { return visibility(i, d.length) });
But this always passes in i = 0 in addition to the seemingly rewriting of the call to arcTween ().
I have also tried
.on("mouseover", function (d, i) {
return function {
arcTween(outerRadius, 0);
visibility(i, d.length);
}
})
Anyone have any advice? (I am using v3 because all the tutorials online are out of date.)
Thank!
EDIT: code snippet
// This data will be gathered from API calls eventually
dataDefault = [];
dataController = [{ "label": "Example 1", "value": 1, "child": [{ "label": "Child 1", "value": 1 }] },
{ "label": "Example 2", "value": 1, "child": [{ "label": "Child 1", "value": 1 }] },
{ "label": "Example 3", "value": 1, "child": [{ "label": "Child 1", "value": 1 }] },
{ "label": "Example 4", "value": 1, "child": [{ "label": "Child 1", "value": 1 }] },
{ "label": "Example 5", "value": 1, "child": [{ "label": "Child 1", "value": 1 }] }];
var displaySize = 20;
// This is used to keep track of what data is showing
var mode = "Default";
// The amount of pixels the SVG will take up
var width = 600,
height = 675;
// It a donut, so it has an outer radius and an inner radius. 2r = width so r = width/2
var outerRadius = width / 2,
innerRadius = outerRadius / 3;
// Default color function for deciding the colros of the donut slices
var color = d3.scale.category10();
// The pie function for deciding the size of the donut slices
var pie = d3.layout.pie()
.value(function (d) { return d["value"]; });
// At first we use the default data to create the pie
var pieData = pie(dataDefault);
// Create an arc
var arc = d3.svg.arc()
.innerRadius(innerRadius);
// Add an SVG tag to the document
var svg = d3.select("#graphs").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + outerRadius + "," + (outerRadius + 50) + ")");
// Append an link tag for each point of the data, then add an path tag inside each a tag
svg.selectAll("a")
.data(pieData)
.enter().append("a")
.append("path")
.each(function (d) { d.outerRadius = outerRadius - 20; })
.attr("d", arc)
.attr("fill", function (d, i) { return color(i); })
.on("mouseover", arcTween(outerRadius, 0, 0))
.on("mouseout", arcTween(outerRadius - 20, 150))
.append("title")
.text(function (d) { return d["value"] + " hits"; });
// Change the default data to the Apps data so it animates on load
changeToAPI("Controller", dataController);
// Function used to increase slice size on hover
function arcTween(outerRadius, delay) {
return function () {
d3.select(this).transition().delay(delay).attrTween("d", function (d) {
var i = d3.interpolate(d.outerRadius, outerRadius);
return function (t) { d.outerRadius = i(t); return arc(d); };
});
};
}
// Passes the color scale into the change function
function getColor(name) {
// Get the remainder when / 3
var bucket = hashify(name) % 4;
// Setup the array of color functions
var colors = [d3.scale.category10(), d3.scale.category20(), d3.scale.category20b(), d3.scale.category20c()];
// Return the correct bucket
return colors[bucket];
}
// Function used to swap the data being shown
function changeToAPI(name, dataSet) {
// Don't update if the data is already showing
// JavaScript doesn't short circuit?
if (dataSet === null) {
dataSet = [{ "label": "No data...", "value": 1 }];
changeTo(name, dataSet);
} else if (dataSet.length === 0) {
dataSet = [{ "label": "No data...", "value": 1 }];
changeTo(name, dataSet);
} else {
mode = name;
// Get the new pie and color functions
var newData = pie(dataSet);
var newColor = getColor(name);
// Remove the labels, titles, and tooltips
svg.selectAll("text").remove();
svg.selectAll("title").remove();
// Line below fixes an error that doesn't cause issues, but makes the graph ugly :(
svg.selectAll("a").remove();
// Add the new slices if there are any
var newSlices = svg.selectAll("a")
.data(newData);
newSlices.enter()
.append("a")
.append("path")
.style("cursor", "pointer");
// Update the attributes of those slices and animate the transition
newSlices.select("path")
.each(function (d) { d.outerRadius = outerRadius - 20; })
.transition()
.attr("d", arc)
.attr("fill", function (d, i) { return newColor(i); })
.attr("title", function (d) { return d["value"]; });
newSlices.selectAll("path")
.on("click", function (d) {
checkForChild(d["data"]["label"], d["data"]);
})
.on("mouseover.arcExpand", arcTween(outerRadius, 0))
.on("mouseover.textHide", function (d, i) {
visibilityHide(i, dataSet.length);
})
.on("mouseout.arcRetract", arcTween(outerRadius - 20, 150))
.on("mouseout.textShow", function (d, i) {
visibilityShow(dataSet.length);
});
// Remove excess slices
newSlices.exit().remove();
// Add a title
var title = svg.append("text")
.attr("x", 0)
.attr("y", -(outerRadius + 10))
.style("text-anchor", "middle")
.text("Distrubution of " + name + " Usage");
// Add labels
var labels = svg.selectAll(null)
.data(newData)
.enter()
.append("text")
.attr("fill", "white")
.attr("id", function (d, i) { return i })
.attr("transform", function (d) {
d.innerRadius = 0;
d.outerRadius = outerRadius;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function (d, i) {
return dataSet[i]["label"];
});
// Add tooltips
svg.selectAll("path").data(newData).append("title").text(function (d) { return d["value"] + " hits"; });
svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", innerRadius)
.style("fill", "white")
.style("cursor", "pointer")
.on("click", function () {
changeToAPI("Controller", dataController);
});
// Adds back button if not at controller level
if (dataSet !== dataController) {
svg.append("text")
.attr("x", 0)
.attr("y", 12)
.style("text-anchor", "middle")
.style("color", "#efefef")
.style("font-size", "40px")
.text("Back");
}
}
}
function changeTo(name, dataSet) {
// Don't update if the data is already showing
// JavaScript doesn't short circuit?
if (dataSet === null) {
dataSet = [{ "label": "No data...", "value": 1 }];
} else if (dataSet.length === 0) {
dataSet = [{ "label": "No data...", "value": 1 }];
}
mode = name;
// Get the new pie and color functions
var newData = pie(dataSet);
var newColor = getColor(name);
// Remove the labels, titles, and tooltips
svg.selectAll("text").remove();
svg.selectAll("title").remove();
// Line below fixes an error that doesn't cause issues, but makes the graph ugly :(
//svg.selectAll("a").remove();
// Add the new slices if there are any
var newSlices = svg.selectAll("a")
.data(newData);
newSlices.enter()
.append("a")
.append("path")
.style("cursor", "pointer");
// Update the attributes of those slices and animate the transition
newSlices.select("path")
.each(function (d) { d.outerRadius = outerRadius - 20; })
.transition()
.attr("fill", function (d, i) { return newColor(i); })
.attr("d", arc)
.attr("title", function (d) { return d["value"]; });
newSlices.selectAll("path")
.on("mouseover.arc", arcTween(outerRadius, 0))
.on("mouseover.text", function (d, i) {
visibilityHide(i, dataSet.length);
})
.on("mouseout.arc", arcTween(outerRadius - 20, 150))
.on("mouseout.text", function (d, i) {
visibilityShow(dataSet.length);
});
// Remove excess slices
newSlices.exit().remove();
// Add a title
svg.append("text")
.attr("x", 0)
.attr("y", -(outerRadius + 10))
.style("text-anchor", "middle")
.text(function (e) {
var title = "Distrubution of " + name + " Usage";
if (name === "Defualt") {
title = "Loading..."
}
return title;
});
// Add labels
svg.selectAll(null)
.data(newData)
.enter()
.append("text")
.attr("fill", "white")
.attr("id", function (d, i) { return i })
.attr("transform", function (d) {
d.innerRadius = 0;
d.outerRadius = outerRadius;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function (d, i) {
return dataSet[i]["label"];
});
// Add tooltips
svg.selectAll("path").data(newData).append("title").text(function (d) { return d["value"] + " hits"; });
}
function checkForChild(name, dataSet) {
if (dataSet.hasOwnProperty("child")) {
if (dataSet["child"] !== null) {
if (dataSet["child"].length !== 0) {
changeToAPI(name, dataSet["child"]);
}
}
}
}
// Hashcode generator for strings
function hashify(string) {
var hash = 0;
// Add the value of each char to the hash value
for (var i = 0; i < string.length; i++) {
hash += string.charCodeAt(i);
}
return hash;
}
function visibilityShow(dataSetSize) {
for (var i = 0; i < dataSetSize; i++) {
$("#" + i).show();
}
}
function visibilityHide(index, dataSetSize) {
for (var i = 0; i < dataSetSize; i++) {
if (i === index) {
$("#" + i).show();
} else {
$("#" + i).hide();
}
}
}
body {
font-family: Arial;
transition: all ease .5s;
text-align: center;
color: rgb(58,58,58);
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>General Statistics</title>
</head>
<body>
<div id="graphs">
</div>
</body>
</html>
source to share
If you have more than one event listener of the same type in the same element, you must namespace listen for your event listeners (but that probably won't solve your problem, please also read the Scriptum post below).
The problem right now, as @AndrewReid explained in his comment , is that the next event listener removes the previous one. According to the API :
If an event listener has already been registered for the same type on the selected item, the existing listener is removed before adding a new listener.
Let's take a look at the next demo.
Since you did not provide your working code, I am creating a simple one here, with two event listeners, the first enlarges the circle and the second fades out the text:
.on("mouseover", increaseCircle)//this one will not work!
.on("mouseover", fadeText)//only this one will work...
You can see that only the last one works:
var svg = d3.select("svg");
var circle = svg.append("circle")
.attr("r", 20)
.attr("cx", 100)
.attr("cy", 50)
.attr("fill", "tan")
.attr("stroke", "black")
.on("mouseover", increaseCircle)
.on("mouseover", fadeText)
.on("mouseout", function() {
circle.transition().duration(500).attr("r", 20);
text.transition().duration(500).style("opacity", 1);
})
var text = svg.append("text")
.attr("y", 55)
.attr("x", 150)
.style("font-family", "helvetica")
.text("Hover over the circle");
function increaseCircle() {
circle.transition().duration(500).attr("r", 40)
}
function fadeText() {
text.transition().duration(500).style("opacity", 0)
}
<script src="https://d3js.org/d3.v3.js"></script>
<svg></svg>
Decision:
However, there is a very simple solution. According to the same API:
To register multiple listeners for the same event type, the type can be followed by an optional namespace such as "click.foo" and "click.bar"
So in the example above, we just need something like this:
.on("mouseover.circle", increaseCircle)
.on("mouseover.text", fadeText)
Here is a demo, both event listeners are working:
var svg = d3.select("svg");
var circle = svg.append("circle")
.attr("r", 20)
.attr("cx", 100)
.attr("cy", 50)
.attr("fill", "tan")
.attr("stroke", "black")
.on("mouseover.circle", increaseCircle)
.on("mouseover.text", fadeText)
.on("mouseout", function() {
circle.transition().duration(500).attr("r", 20);
text.transition().duration(500).style("opacity", 1);
})
var text = svg.append("text")
.attr("y", 55)
.attr("x", 150)
.style("font-family", "helvetica")
.text("Hover over the circle");
function increaseCircle() {
circle.transition().duration(500).attr("r", 40)
}
function fadeText() {
text.transition().duration(500).style("opacity", 0)
}
<script src="https://d3js.org/d3.v3.js"></script>
<svg></svg>
Of course, a simple alternative to all of this is fair:
selection.on("mouseover", function(){
foo();
bar();
baz();
etc...
});
PS . The above answer addresses the namespace issue. However, aside from this problem, your code has several problems that we cannot verify because you did not provide a working demo.
First problem: when you do this ...
.on("mouseover", arcTween(outerRadius, 0, 0))
... you call arcTween
immediately and pass its value to the listener. You probably want:
.on("mouseover", function(){ arcTween(outerRadius, 0, 0)})
Second, this is not true:
.on("mouseover", function (d, i) {
return function {
arcTween(outerRadius, 0);
visibility(i, d.length);
}
})
It should be simple:
.on("mouseover", function (d, i) {
arcTween(outerRadius, 0);
visibility(i, d.length);
})
source to share