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>
      

Run codeHide result


+3


source to share


1 answer


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>
      

Run codeHide result


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>
      

Run codeHide result


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

      

+3


source







All Articles