javascript d3.js 散点图 - 缩放/拖动边界、缩放按钮、重置缩放、计算中值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15069959/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 23:23:16  来源:igfitidea点击:

d3.js scatter plot - zoom/drag boundaries, zoom buttons, reset zoom, calculate median

javascriptd3.jszoomdragscatter-plot

提问by richardwestenra

I've built a d3.js scatter plot with zoom/pan functionality. You can see the full thing here (click 'Open in a new window' to see the whole thing): http://bl.ocks.org/129f64bfa2b0d48d27c9

我已经构建了一个带有缩放/平移功能的 d3.js 散点图。您可以在此处查看完整内容(单击“在新窗口中打开”以查看完整内容):http: //bl.ocks.org/129f64bfa2b0d48d27c9

There are a couple of features that I've been unable to figure out, that I'd love a hand with it if someone can point me in the right direction:

有几个功能我一直无法弄清楚,如果有人能指出我正确的方向,我会喜欢它:

  1. I want to apply X/Y zoom/pan boundaries to the area, so that you can't drag it below a certain point (e.g. zero).
  2. I've also made a stab at creating Google Maps style +/- zoom buttons, without any success. Any ideas?
  1. 我想将 X/Y 缩放/平移边界应用于该区域,以便您无法将其拖动到某个点(例如零)以下。
  2. 我还尝试创建 Google 地图样式的 +/- 缩放按钮,但没有成功。有任何想法吗?

Much less importantly, there are also a couple of areas where I've figured out a solution but it's very rough, so if you have a better solution then please do let me know:

更不重要的是,还有几个方面我已经找到了解决方案,但非常粗糙,所以如果您有更好的解决方案,请告诉我:

  1. I've added a 'reset zoom' button but it merely deletes the graph and generates a new one in its place, rather than actually zooming the objects. Ideally it should actually reset the zoom.
  2. I've written my own function to calculate the median of the X and Y data. However I'm sure that there must be a better way to do this with d3.median but I can't figure out how to make it work.

    var xMed = median(_.map(data,function(d){ return d.TotalEmployed2011;}));
    var yMed = median(_.map(data,function(d){ return d.MedianSalary2011;}));
    
    function median(values) {
        values.sort( function(a,b) {return a - b;} );
        var half = Math.floor(values.length/2);
    
        if(values.length % 2)
            return values[half];
        else
            return (parseFloat(values[half-1]) + parseFloat(values[half])) / 2.0;
    };
    
  1. 我添加了一个“重置缩放”按钮,但它只是删除图形并在其位置生成一个新图形,而不是实际缩放对象。理想情况下,它实际上应该重置缩放。
  2. 我已经编写了自己的函数来计算 X 和 Y 数据的中值。但是我确信必须有更好的方法来使用 d3.median 来做到这一点,但我不知道如何使它工作。

    var xMed = median(_.map(data,function(d){ return d.TotalEmployed2011;}));
    var yMed = median(_.map(data,function(d){ return d.MedianSalary2011;}));
    
    function median(values) {
        values.sort( function(a,b) {return a - b;} );
        var half = Math.floor(values.length/2);
    
        if(values.length % 2)
            return values[half];
        else
            return (parseFloat(values[half-1]) + parseFloat(values[half])) / 2.0;
    };
    

A very simplified (i.e. old) version of the JS is below. You can find the full script at https://gist.github.com/richardwestenra/129f64bfa2b0d48d27c9#file-main-js

下面是一个非常简化(即旧)的 JS 版本。你可以在https://gist.github.com/richardwestenra/129f64bfa2b0d48d27c9#file-main-js找到完整的脚本

d3.csv("js/AllOccupations.csv", function(data) {

    var margin = {top: 30, right: 10, bottom: 50, left: 60},
        width = 960 - margin.left - margin.right,
        height = 500 - margin.top - margin.bottom;

    var xMax = d3.max(data, function(d) { return +d.TotalEmployed2011; }),
        xMin = 0,
        yMax = d3.max(data, function(d) { return +d.MedianSalary2011; }),
        yMin = 0;

    //Define scales
    var x = d3.scale.linear()
        .domain([xMin, xMax])
        .range([0, width]);

    var y = d3.scale.linear()
        .domain([yMin, yMax])
        .range([height, 0]);

    var colourScale = function(val){
        var colours = ['#9d3d38','#c5653a','#f9b743','#9bd6d7'];
        if (val > 30) {
            return colours[0];
        } else if (val > 10) {
            return colours[1];
        } else if (val > 0) {
            return colours[2];
        } else {
            return colours[3];
        }
    };


    //Define X axis
    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .tickSize(-height)
        .tickFormat(d3.format("s"));

    //Define Y axis
    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left")
        .ticks(5)
        .tickSize(-width)
        .tickFormat(d3.format("s"));

    var svg = d3.select("#chart").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .call(d3.behavior.zoom().x(x).y(y).scaleExtent([1, 8]).on("zoom", zoom));

    svg.append("rect")
        .attr("width", width)
        .attr("height", height);

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

    // Create points
    svg.selectAll("polygon")
        .data(data)
        .enter()
        .append("polygon")
        .attr("transform", function(d, i) {
            return "translate("+x(d.TotalEmployed2011)+","+y(d.MedianSalary2011)+")";
        })
        .attr('points','4.569,2.637 0,5.276 -4.569,2.637 -4.569,-2.637 0,-5.276 4.569,-2.637')
        .attr("opacity","0.8")
        .attr("fill",function(d) {
            return colourScale(d.ProjectedGrowth2020);
        });

    // Create X Axis label
    svg.append("text")
        .attr("class", "x label")
        .attr("text-anchor", "end")
        .attr("x", width)
        .attr("y", height + margin.bottom - 10)
        .text("Total Employment in 2011");

    // Create Y Axis label
    svg.append("text")
        .attr("class", "y label")
        .attr("text-anchor", "end")
        .attr("y", -margin.left)
        .attr("x", 0)
        .attr("dy", ".75em")
        .attr("transform", "rotate(-90)")
        .text("Median Annual Salary in 2011 ($)");


    function zoom() {
      svg.select(".x.axis").call(xAxis);
      svg.select(".y.axis").call(yAxis);
      svg.selectAll("polygon")
            .attr("transform", function(d) {
                return "translate("+x(d.TotalEmployed2011)+","+y(d.MedianSalary2011)+")";
            });
    };
    }
});

Any help would be massively appreciated. Thanks!

任何帮助将不胜感激。谢谢!

Edit: Here is a summary of the fixes I used, based on Superboggly's suggestions below:

编辑:以下是我使用的修复程序的摘要,基于 Superboggly 的以下建议:

    // Zoom in/out buttons:
    d3.select('#zoomIn').on('click',function(){
        d3.event.preventDefault();
        if (zm.scale()< maxScale) {
            zm.translate([trans(0,-10),trans(1,-350)]);
            zm.scale(zm.scale()*2);
            zoom();
        }
    });
    d3.select('#zoomOut').on('click',function(){
        d3.event.preventDefault();
        if (zm.scale()> minScale) {
            zm.scale(zm.scale()*0.5);
            zm.translate([trans(0,10),trans(1,350)]);
            zoom();
        }
    });
    // Reset zoom button:
    d3.select('#zoomReset').on('click',function(){
        d3.event.preventDefault();
        zm.scale(1);
        zm.translate([0,0]);
        zoom();
    });


    function zoom() {

        // To restrict translation to 0 value
        if(y.domain()[0] < 0 && x.domain()[0] < 0) {
            zm.translate([0, height * (1 - zm.scale())]);
        } else if(y.domain()[0] < 0) {
            zm.translate([d3.event.translate[0], height * (1 - zm.scale())]);
        } else if(x.domain()[0] < 0) {
            zm.translate([0, d3.event.translate[1]]);
        }
        ...
    };

The zoom translation that I used is very ad hoc and basically uses abitrary constants to keep the positioning more or less in the right place. It's not ideal, and I'd be willing to entertain suggestions for a more universally sound technique. However, it works well enough in this case.

我使用的缩放平移非常特别,基本上使用任意常量来或多或少地将定位保持在正确的位置。这并不理想,我愿意接受对更普遍的声音技术的建议。但是,在这种情况下它工作得很好。

回答by Superboggly

To start with the median function just takes an array and an optional accessor. So you can use it the same way you use max:

首先,中值函数只需要一个数组和一个可选的访问器。所以你可以像使用 max 一样使用它:

var med = d3.median(data, function(d) { return +d.TotalEmployed2011; });

As for the others if you pull out your zoom behaviour you can control it a bit better. So for example instead of

至于其他的,如果你拉出你的缩放行为,你可以更好地控制它。所以例如而不是

var svg = d3.select()...call(d3.behavior.zoom()...) 

try:

尝试:

var zm = d3.behavior.zoom().x(x).y(y).scaleExtent([1, 8]).on("zoom", zoom);
var svg = d3.select()...call(zm);

Then you can set the zoom level and translation directly:

然后就可以直接设置缩放级别和平移了:

function zoomIn() {
   zm.scale(zm.scale()*2);
   // probably need to compute a new translation also
}

function reset() {
   zm.scale(1);
   zm.translate([0,0]);
}

Restricting the panning range is a bit trickier. You can simply not update when the translate or scale is not to your liking inside you zoom function (or set the zoom's "translate" to what you need it to be). Something like (I think in your case):

限制平移范围有点棘手。当缩放功能中的平移或比例不符合您的喜好时,您可以简单地不更新(或将缩放的“平移”设置为您需要的值)。像(我认为在你的情况下):

function zoom() {
    if(y.domain()[0] < 0) {
        // To restrict translation to 0 value
        zm.translate([d3.event.translate[0], height * (1 - zm.scale())]);
    }
    ....
}        

Keep in mind that if you want zooming in to allow a negative on the axis, but panning not to you will find you get into some tricky scenarios.

请记住,如果您想放大以允许轴上的负数,但不平移到您会发现您会遇到一些棘手的情况。

This might be dated, but check out Limiting domain when zooming or panning in D3.js

这可能已经过时,但请查看在 D3.js 中缩放或平移时的限制域

Note also that the zoom behaviour did have functionality for limiting panning and zooming at one point. But the code was taken out in a later update.

另请注意,缩放行为确实具有在某一点限制平移和缩放的功能。但是代码在后来的更新中被去掉了。

回答by Csaba Toth

I don't like to reinvent the wheel. I was searching for scatter plots which allow zooming. Highcharts is one of them, but there's plotly, which is based on D3 and not only allows zooming, but you can also have line datasets too on the scatter plot, which I desire with some of my datasets, and that's hard to find with other plot libraries. I'd give it a try:

我不喜欢重新发明轮子。我正在寻找允许缩放的散点图。Highcharts 是其中之一,但有 plotly,它基于 D3,不仅允许缩放,而且您还可以在散点图上使用线数据集,我希望使用我的一些数据集,而其他数据集很难找到绘图库。我想试一试:

https://plot.ly/javascript/line-and-scatter/

https://plot.ly/javascript/line-and-scatter/

https://github.com/plotly/plotly.js

https://github.com/plotly/plotly.js

Using such nice library can save you a lot of time and pain.

使用这么好的库可以为您节省大量时间和痛苦。