【问题标题】:d3.js voronoi event. Mouseover seems to disappear when cursor is exactly above pointd3.js voronoi 事件。当光标正好在点上方时,鼠标悬停似乎消失了
【发布时间】:2019-01-15 03:30:01
【问题描述】:

请问我在这里做错了什么?当鼠标进入相关的 voronoi 单元格时,我想增加点的大小,但是当鼠标完全高于该点时,该点会恢复到其原始大小;我已经尝试了mouseovermousemove 事件,但没有任何运气。 sn -p 中的代码,你可以放大看到我刚才描述的内容。 非常感谢!

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">

<title>Chart</title>


<!-- Reference minified version of D3 -->
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
</head>

<body>
    <style>
        .grid line {
            stroke: #ddd;
        }
    </style>


<div id='scatter-plot'>
<svg width="700" height="500">
</svg>
</div>

   
<script>

            var data = [];
                for (let i = 0; i < 200; i++) {
                data.push({
                    x: Math.random(),
                    y: Math.random(),
                    dotNum: i,
                })
            }
                
        renderChart(data)
 
    

        
function renderChart(data) {

    var totalWidth = 920,
        totalHeight = 480;
    
    var margin = {
            top: 10,
            left: 50,
            bottom: 30,
            right: 0
        }
    
    var width = totalWidth  - margin.left - margin.right,
        height = totalHeight  - margin.top - margin.bottom;

    // inner chart dimensions, where the dots are plotted
//    var width = width - margin.left - margin.right;
//    var height = height - margin.top - margin.bottom;
    
     var tsn = d3.transition().duration(200);

    // radius of points in the scatterplot
    var pointRadius = 2;

    var extent = {
        x: d3.extent(data, function (d) {return d.x}),
        y: d3.extent(data, function (d) {return d.y}),
    };

    var scale = {
        x: d3.scaleLinear().range([0, width]),
        y: d3.scaleLinear().range([height, 0]),
    };

    var axis = {
        x: d3.axisBottom(scale.x).ticks(xTicks).tickSizeOuter(0),
        y: d3.axisLeft(scale.y).ticks(yTicks).tickSizeOuter(0),
    };
    
    var gridlines = {
        x: d3.axisBottom(scale.x).tickFormat("").tickSize(height),
        y: d3.axisLeft(scale.y).tickFormat("").tickSize(-width),
    }
    

    var colorScale = d3.scaleLinear().domain([0, 1]).range(['#06a', '#06a']);

    // select the root container where the chart will be added
    var container = d3.select('#scatter-plot');

    var zoom = d3.zoom()
        .scaleExtent([1, 20])
        .on("zoom", zoomed);
    
    var tooltip = d3.select("body").append("div")
        .attr("id", "tooltip")
        .style("opacity", 0);

    // initialize main SVG
    var svg = container.select('svg')
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .call(zoom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
    // Clip path
    svg.append("clipPath")
        .attr("id", "clip")
        .append("rect")
        .attr("width", width)
        .attr("height", height);


    // Heatmap dots
    var dotsGroup = svg.append("g")
        .attr("clip-path", "url(#clip)")
        .append("g");
    
    //Create X axis
    var renderXAxis = svg.append("g")
        .attr("class", "x axis")

    //Create Y axis
    var renderYAxis = svg.append("g")
        .attr("class", "y axis")


    // set up axis generating functions
    var xTicks = Math.round(width / 50);
    var yTicks = Math.round(height / 50);

    
    function updateScales(data, scale){
        scale.x.domain([extent.x[0], extent.x[1]]).nice(),
        scale.y.domain([extent.y[0], extent.y[1]]).nice()
    }

    function zoomed() {
        d3.event.transform.x = d3.event.transform.x;
        d3.event.transform.y = d3.event.transform.y;

        // update: rescale x axis
        renderXAxis.call(axis.x.scale(d3.event.transform.rescaleX(scale.x)));
        renderYAxis.call(axis.y.scale(d3.event.transform.rescaleX(scale.y)));


        dotsGroup.attr("transform", d3.event.transform);
    }
    
    // add the overlay on top of everything to take the mouse events
    dotsGroup.append('rect')
        .attr('class', 'overlay')
        .attr('width', width)
        .attr('height', height)
        .style('fill', 'red')
        .style('opacity', 0)
        .on('mouseover', mouseMoveHandler)
        .on('mouseleave', () => {
            // hide the highlight circle when the mouse leaves the chart
            highlight(null);
    });
    
    renderPlot(data);
    
    function renderPlot(data){
        updateScales(data, scale);
        
        svg.select('.y.axis')
            .attr("transform", "translate(" + -pointRadius + " 0)" )
            .call(axis.y);
        
        var h = height + pointRadius;
        svg.select('.x.axis')
            .attr("transform", "translate(0, " + h + ")")
            .call(axis.x);
        
        svg.append("g")
            .attr("class", "grid")
            .call(gridlines.x);
        
        svg.append("g")
            .attr("class", "grid")
            .call(gridlines.y);
        
        
        //Do the chart
        var update = dotsGroup.selectAll("circle").data(data)
        
        update
            .enter()
            .append('circle')
            .attr('r', pointRadius)
            .attr('cx', d => scale.x(d.x))
            .attr('cy', d => scale.y(d.y))
            .attr('fill', d => colorScale(d.y))
    };


    // create a voronoi diagram 
    var voronoiDiagram = d3.voronoi()
        .x(d => scale.x(d.x))
        .y(d => scale.y(d.y))
        .size([width, height])(data);


    // add a circle for indicating the highlighted point
    dotsGroup.append('circle')
        .attr('class', 'highlight-circle')
        .attr('r', pointRadius*2) // increase the size if highlighted
        .style('fill', 'red')
        .style('display', 'none');

    // callback to highlight a point
    function highlight(d) {
        // no point to highlight - hide the circle and the tooltip
        if (!d) {
            d3.select('.highlight-circle').style('display', 'none');
            //tooltip.style("opacity",0);
            // otherwise, show the highlight circle at the correct position
        } else {
            d3.select('.highlight-circle')
                .style('display', '')
                .style('stroke', colorScale(d.y))
                .attr('cx', scale.x(d.x))
                .attr('cy', scale.y(d.y));
        }
    }

    // callback for when the mouse moves across the overlay
    function mouseMoveHandler() {
        // get the current mouse position
        var [mx, my] = d3.mouse(this);

        var site = voronoiDiagram.find(mx, my);

        // highlight the point if we found one, otherwise hide the highlight circle
        highlight(site && site.data);


        for (let i = 0; i < site.data.dotNum; i++) {
            //do something....
        }
        
    }




}



    
</script>
</body>

</html>

【问题讨论】:

    标签: javascript d3.js mouseevent mouseover voronoi


    【解决方案1】:
    • 您必须在圆圈和高亮圆圈之后绘制overlay 矩形。如果不是,则将鼠标悬停在一个圆圈上会生成一个鼠标离开事件,并且您会看到高亮圆圈的闪烁
    • 使用mousemove 事件而不是mouseover,这是一种鼠标输入 事件
    • 我添加了逻辑,仅在高亮更改点时才更新高亮
    • 缩放和平移时网格未更新(未修复)
    • 即使在overlay 上移动时,仍然有mouseleave 事件——它们是由网格线引起的。将点组移到网格线组之后

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
    <meta charset="utf-8">
    
    <title>Chart</title>
    
    
    <!-- Reference minified version of D3 -->
    <script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
    </head>
    
    <body>
    <style>
    .grid line { stroke: #ddd; }
    </style>
    
    <div id='scatter-plot'>
    <svg width="700" height="500">
    </svg>
    </div>
    
    <script>
    
    var data = [];
        for (let i = 0; i < 200; i++) {
        data.push({
            x: Math.random(),
            y: Math.random(),
            dotNum: i,
        })
    }
    renderChart(data);
    
    function renderChart(data) {
    
        var totalWidth = 920,
            totalHeight = 480;
    
        var margin = {
                top: 10,
                left: 50,
                bottom: 30,
                right: 0
            }
    
        var width = totalWidth  - margin.left - margin.right,
            height = totalHeight  - margin.top - margin.bottom;
    
        // inner chart dimensions, where the dots are plotted
    //    var width = width - margin.left - margin.right;
    //    var height = height - margin.top - margin.bottom;
    
         var tsn = d3.transition().duration(200);
    
        // radius of points in the scatterplot
        var pointRadius = 2;
    
        var extent = {
            x: d3.extent(data, function (d) {return d.x}),
            y: d3.extent(data, function (d) {return d.y}),
        };
    
        var scale = {
            x: d3.scaleLinear().range([0, width]),
            y: d3.scaleLinear().range([height, 0]),
        };
    
        var axis = {
            x: d3.axisBottom(scale.x).ticks(xTicks).tickSizeOuter(0),
            y: d3.axisLeft(scale.y).ticks(yTicks).tickSizeOuter(0),
        };
        
        var gridlines = {
            x: d3.axisBottom(scale.x).tickFormat("").tickSize(height),
            y: d3.axisLeft(scale.y).tickFormat("").tickSize(-width),
        }
    
        var colorScale = d3.scaleLinear().domain([0, 1]).range(['#06a', '#06a']);
    
        // select the root container where the chart will be added
        var container = d3.select('#scatter-plot');
    
        var zoom = d3.zoom()
            .scaleExtent([1, 20])
            .on("zoom", zoomed);
    
        var tooltip = d3.select("body").append("div")
            .attr("id", "tooltip")
            .style("opacity", 0);
    
        // initialize main SVG
        var svg = container.select('svg')
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .call(zoom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
        // Clip path
        svg.append("clipPath")
            .attr("id", "clip")
            .append("rect")
            .attr("width", width)
            .attr("height", height);
    
        //Create X axis
        var renderXAxis = svg.append("g")
            .attr("class", "x axis")
    
        //Create Y axis
        var renderYAxis = svg.append("g")
            .attr("class", "y axis")
    
        // set up axis generating functions
        var xTicks = Math.round(width / 50);
        var yTicks = Math.round(height / 50);
    
        function updateScales(data, scale){
            scale.x.domain([extent.x[0], extent.x[1]]).nice(),
            scale.y.domain([extent.y[0], extent.y[1]]).nice()
        }
    
        function zoomed() {
            d3.event.transform.x = d3.event.transform.x;
            d3.event.transform.y = d3.event.transform.y;
    
            // update: rescale x axis
            renderXAxis.call(axis.x.scale(d3.event.transform.rescaleX(scale.x)));
            renderYAxis.call(axis.y.scale(d3.event.transform.rescaleX(scale.y)));
    
            dotsGroup.attr("transform", d3.event.transform);
        }
    
        var dotsGroup;
        renderPlot(data);
    
        function renderPlot(data){
            updateScales(data, scale);
            
            svg.select('.y.axis')
                .attr("transform", "translate(" + -pointRadius + " 0)" )
                .call(axis.y);
            
            var h = height + pointRadius;
            svg.select('.x.axis')
                .attr("transform", "translate(0, " + h + ")")
                .call(axis.x);
            
            svg.append("g")
                .attr("class", "grid")
                .call(gridlines.x);
            
            svg.append("g")
                .attr("class", "grid")
                .call(gridlines.y);
    
            dotsGroup = svg.append("g")
                           .attr("clip-path", "url(#clip)")
                           .append("g");
            
            //Do the chart
            var update = dotsGroup.selectAll("circle").data(data)
            
            update
                .enter()
                .append('circle')
                .attr('r', pointRadius)
                .attr('cx', d => scale.x(d.x))
                .attr('cy', d => scale.y(d.y))
                .attr('fill', d => colorScale(d.y))
        };
    
        // create a voronoi diagram 
        var voronoiDiagram = d3.voronoi()
            .x(d => scale.x(d.x))
            .y(d => scale.y(d.y))
            .size([width, height])(data);
    
        // add a circle for indicating the highlighted point
        dotsGroup.append('circle')
            .attr('class', 'highlight-circle')
            .attr('r', pointRadius*2) // increase the size if highlighted
            .style('fill', 'red')
            .style('display', 'none');
    
        // add the overlay on top of everything to take the mouse events
        dotsGroup.append('rect')
            .attr('class', 'overlay')
            .attr('width', width)
            .attr('height', height)
            .style('fill', 'red')
            .style('opacity', 0)
            .on('mousemove', mouseMoveHandler)
            .on('mouseleave', () => {
                // hide the highlight circle when the mouse leaves the chart
                console.log('mouse leave');
                highlight(null);
        });
    
        var prevHighlightDotNum = null;
        // callback to highlight a point
        function highlight(d) {
            // no point to highlight - hide the circle and the tooltip
            if (!d) {
                d3.select('.highlight-circle').style('display', 'none');
                prevHighlightDotNum = null;
                //tooltip.style("opacity",0);
                // otherwise, show the highlight circle at the correct position
            } else {
                if (prevHighlightDotNum !== d.dotNum) {
                    d3.select('.highlight-circle')
                      .style('display', '')
                      .style('stroke', colorScale(d.y))
                      .attr('cx', scale.x(d.x))
                      .attr('cy', scale.y(d.y));
                    prevHighlightDotNum = d.dotNum;
                }
            }
        }
    
        // callback for when the mouse moves across the overlay
        function mouseMoveHandler() {
            // get the current mouse position
            var [mx, my] = d3.mouse(this);
    
            var site = voronoiDiagram.find(mx, my);
    
            //console.log('site', site);
            // highlight the point if we found one, otherwise hide the highlight circle
            highlight(site && site.data);
    
            for (let i = 0; i < site.data.dotNum; i++) {
                //do something....
            }
        }
    }
    </script>
    </body>
    
    </html>

    【讨论】:

    • 谢谢一百万!!这真是太好了!!感谢您指出由网格线引起的事件,我完全没有意识到这一点。我欠你一杯啤酒(或者可能是一卡车啤酒,这听起来更公平......)
    猜你喜欢
    • 2021-09-23
    • 2015-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-14
    • 2013-09-30
    • 1970-01-01
    相关资源
    最近更新 更多