Michael Hall
Michael Hall

Reputation: 3330

D3 'mouseenter' and zoom simultaneously

I have a scatterplot that has zoom functioning perfectly. I am trying to add a tooltip such that on 'mouseenter' on a <circle> element, the tooltip fires. I have this working, i.e the 'mouseenter' event is called but I cannot zoom while the mouse stays on this <circle>. Is there a way to get them to both occur at the same time?

Here is a minimal version of the code that reproduces the issue.

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<button id="resetBtn">Reset</button>
<div id="chart"></div>
<script>
    var data = [
        {
            x: 0.5, y: 0.5
        },
        {
            x: 0.6, y: 0.6
        },
        {
            x: 0.45, y: 0.65
        },
        {
            x: 0.76, y: 0.61
        },
        {
            x: 0.51, y: 0.05
        },
        {
            x: 0.16, y: 6.8
        }
    ];

    var plot = volcanoPlot()
        .xColumn("x")
        .yColumn("y");

    d3.select('#chart')
        .data([data])
        .call(plot);

    function volcanoPlot() {
        var width = 960,
            height = 500,
            margin = {top: 20, right: 20, bottom: 40, left: 50},
            xColumn,
            dotRadius = 10,
            yColumn,
            xScale = d3.scaleLinear(),
            yScale = d3.scaleLog();

        function chart(selection){
            var innerWidth = width - margin.left - margin.right, // set the size of the chart within its container
                innerHeight = height - margin.top - margin.bottom;

            selection.each(function(data) {

                xScale.range([0, innerWidth])
                    .domain(d3.extent(data, function(d) { return d[xColumn]; }))
                    .nice();

                yScale.range([0, innerHeight])
                    .domain(d3.extent(data, function(d) { return d[yColumn]; }))
                    .nice();

                var zoom = d3.zoom()
                    .scaleExtent([1, 20])
                    .translateExtent([[0, 0], [width, height]])
                    .on('zoom', zoomFunction);

                var svg = d3.select(this).append('svg')
                    .attr('height', height)
                    .attr('width', width)
                  .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

                d3.select('#resetBtn')
                    .style('top', margin.top * 1.5 + 'px')
                    .style('left', margin.left * 1.25 + 'px')
                    .on('click', reset);

                svg.append('defs').append('clipPath')
                    .attr('id', 'clip')
                  .append('rect')
                    .attr('height', innerHeight)
                    .attr('width', innerWidth);

                // add the axes
                var xAxis = d3.axisBottom(xScale);
                var yAxis = d3.axisLeft(yScale);

                var gX = svg.append('g')
                    .attr('class', 'x axis')
                    .attr('transform', 'translate(0,' + innerHeight + ')')
                    .call(xAxis);

                gX.append('text')
                    .attr('class', 'label')
                    .attr('transform', 'translate(' + width / 2 + ',' + (margin.bottom - 6) + ')')
                    .attr('text-anchor', 'middle')
                    .text(xColumn);

                var gY = svg.append('g')
                    .attr('class', 'y axis')
                    .call(yAxis);

                gY.append('text')
                    .attr('class', 'label')
                    .attr('transform', 'translate(' + (0 - margin.left / 1.5) + ',' + (height / 2) + ') rotate(-90)')
                    .style('text-anchor', 'middle')
                    .text(yColumn);

                var zoomBox = svg.append('rect')
                    .attr('class', 'zoom')
                    .attr('height', innerHeight)
                    .attr('width', innerWidth)
                    .attr('fill', 'none')
                    .attr('pointer-events', 'all')
                    .call(zoom);

                var circles = svg.append('g')
                    .attr('class', 'circlesContainer')
                    .attr('clip-path', 'url(#clip)');

                circles.selectAll(".dot")
                    .data(data)
                    .enter().append('circle')
                    .attr('r', dotRadius)
                    .attr('cx', function(d) { return xScale(d[xColumn]); })
                    .attr('cy', function(d) { return yScale(d[yColumn]); })
                    .attr('class', 'dot')
                    .attr('stroke', 'none')
                    .on('mouseenter', function(){
                        console.log("hi");
                    });

                function zoomFunction() {
                    var transform = d3.zoomTransform(this);
                    d3.selectAll('.dot')
                        .attr('transform', transform)
                        .attr('r', dotRadius / Math.sqrt(transform.k));
                    gX.call(xAxis.scale(d3.event.transform.rescaleX(xScale)));
                    gY.call(yAxis.scale(d3.event.transform.rescaleY(yScale)));
                }

                function reset() {
                    var ease = d3.easePolyIn.exponent(4.0);
                    d3.select('.zoom')
                        .transition().duration(750)
                        .ease(ease)
                        .call(zoom.transform, d3.zoomIdentity);
                }
            });
        }

        chart.width = function(value) {
            if (!arguments.length) return width;
            width = value;
            return chart;
        };

        chart.height = function(value) {
            if (!arguments.length) return height;
            height = value;
            return chart;
        };

        chart.margin = function(value) {
            if (!arguments.length) return margin;
            margin = value;
            return chart;
        };

        chart.xColumn = function(value) {
            if (!arguments.length) return xColumn;
            xColumn = value;
            return chart;
        };

        chart.yColumn = function(value) {
            if (!arguments.length) return yColumn;
            yColumn = value;
            return chart;
        };
        return chart;
    }
</script>
</body>
</html>

Upvotes: 1

Views: 343

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

You're calling your zoom function on the rectangle and, because of that, the zoom will not work when you hover over the circles. It has nothing to do with the mouseenter function: you can remove the mouseenter and the zoom still won't work when you hover over the circles.

A simple solution is calling the zoom on your SVG group, not on the rectangle:

var svg = d3.select(this).append('svg')
    .attr('height', height)
    .attr('width', width)
    .append('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
    .call(zoom);

Here is your code with that change only:

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<button id="resetBtn">Reset</button>
<div id="chart"></div>
<script>
    var data = [
        {
            x: 0.5, y: 0.5
        },
        {
            x: 0.6, y: 0.6
        },
        {
            x: 0.45, y: 0.65
        },
        {
            x: 0.76, y: 0.61
        },
        {
            x: 0.51, y: 0.05
        },
        {
            x: 0.16, y: 6.8
        }
    ];

    var plot = volcanoPlot()
        .xColumn("x")
        .yColumn("y");

    d3.select('#chart')
        .data([data])
        .call(plot);

    function volcanoPlot() {
        var width = 960,
            height = 500,
            margin = {top: 20, right: 20, bottom: 40, left: 50},
            xColumn,
            dotRadius = 10,
            yColumn,
            xScale = d3.scaleLinear(),
            yScale = d3.scaleLog();

        function chart(selection){
            var innerWidth = width - margin.left - margin.right, // set the size of the chart within its container
                innerHeight = height - margin.top - margin.bottom;

            selection.each(function(data) {

                xScale.range([0, innerWidth])
                    .domain(d3.extent(data, function(d) { return d[xColumn]; }))
                    .nice();

                yScale.range([0, innerHeight])
                    .domain(d3.extent(data, function(d) { return d[yColumn]; }))
                    .nice();

                var zoom = d3.zoom()
                    .scaleExtent([1, 20])
                    .translateExtent([[0, 0], [width, height]])
                    .on('zoom', zoomFunction);

                var svg = d3.select(this).append('svg')
                    .attr('height', height)
                    .attr('width', width)
                  .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
                    .call(zoom);

                d3.select('#resetBtn')
                    .style('top', margin.top * 1.5 + 'px')
                    .style('left', margin.left * 1.25 + 'px')
                    .on('click', reset);

                svg.append('defs').append('clipPath')
                    .attr('id', 'clip')
                  .append('rect')
                    .attr('height', innerHeight)
                    .attr('width', innerWidth);

                // add the axes
                var xAxis = d3.axisBottom(xScale);
                var yAxis = d3.axisLeft(yScale);

                var gX = svg.append('g')
                    .attr('class', 'x axis')
                    .attr('transform', 'translate(0,' + innerHeight + ')')
                    .call(xAxis);

                gX.append('text')
                    .attr('class', 'label')
                    .attr('transform', 'translate(' + width / 2 + ',' + (margin.bottom - 6) + ')')
                    .attr('text-anchor', 'middle')
                    .text(xColumn);

                var gY = svg.append('g')
                    .attr('class', 'y axis')
                    .call(yAxis);

                gY.append('text')
                    .attr('class', 'label')
                    .attr('transform', 'translate(' + (0 - margin.left / 1.5) + ',' + (height / 2) + ') rotate(-90)')
                    .style('text-anchor', 'middle')
                    .text(yColumn);

                var zoomBox = svg.append('rect')
                    .attr('class', 'zoom')
                    .attr('height', innerHeight)
                    .attr('width', innerWidth)
                    .attr('fill', 'none')
                    .attr('pointer-events', 'all');

                var circles = svg.append('g')
                    .attr('class', 'circlesContainer')
                    .attr('clip-path', 'url(#clip)');

                circles.selectAll(".dot")
                    .data(data)
                    .enter().append('circle')
                    .attr('r', dotRadius)
                    .attr('cx', function(d) { return xScale(d[xColumn]); })
                    .attr('cy', function(d) { return yScale(d[yColumn]); })
                    .attr('class', 'dot')
                    .attr('stroke', 'none')
                    .on('mouseenter', function(){
                        console.log("hi");
                    });

                function zoomFunction() {
                    var transform = d3.zoomTransform(this);
                    d3.selectAll('.dot')
                        .attr('transform', transform)
                        .attr('r', dotRadius / Math.sqrt(transform.k));
                    gX.call(xAxis.scale(d3.event.transform.rescaleX(xScale)));
                    gY.call(yAxis.scale(d3.event.transform.rescaleY(yScale)));
                }

                function reset() {
                    var ease = d3.easePolyIn.exponent(4.0);
                    d3.select('.zoom')
                        .transition().duration(750)
                        .ease(ease)
                        .call(zoom.transform, d3.zoomIdentity);
                }
            });
        }

        chart.width = function(value) {
            if (!arguments.length) return width;
            width = value;
            return chart;
        };

        chart.height = function(value) {
            if (!arguments.length) return height;
            height = value;
            return chart;
        };

        chart.margin = function(value) {
            if (!arguments.length) return margin;
            margin = value;
            return chart;
        };

        chart.xColumn = function(value) {
            if (!arguments.length) return xColumn;
            xColumn = value;
            return chart;
        };

        chart.yColumn = function(value) {
            if (!arguments.length) return yColumn;
            yColumn = value;
            return chart;
        };
        return chart;
    }
</script>
</body>
</html>

Upvotes: 1

Related Questions