d3.js散点图-缩放/拖动边界,缩放按钮,重置缩放,计算中位数

7

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

有几个功能我一直无法解决,如果有人能指点我正确的方向,我将不胜感激:

  1. 我想将X/Y缩放/平移边界应用于该区域,以便您不能将其拖动到某个点以下(例如零)。
  2. 我还尝试创建类似Google Maps样式的+/-缩放按钮,但没有成功。有什么想法吗?

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

  1. 我已经添加了一个“重置缩放”按钮,但它仅仅是删除图表并在原地生成一个新的,而不是实际缩放对象。理想情况下,它应该真正重置缩放。
  2. 我编写了自己的函数来计算X和Y数据的中位数。但是我相信一定有更好的方法可以使用d3.median来做到这一点,但我无法弄清楚如何使其工作。
一个非常简化(即旧)的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)+")";
            });
    };
    }
});

任何帮助都将不胜感激。谢谢!
编辑:以下是我根据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]]);
        }
        ...
    };

我使用的缩放翻译方法非常临时,基本上使用任意常数来保持位置大致正确。这并不理想,我愿意考虑更普遍可靠的技术建议。然而,在这种情况下它已经足够好用了。

2个回答

12

要开始使用中位数函数,只需提供一个数组和一个可选的访问器。因此,您可以像使用最大值函数一样使用它:

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

至于其他的,如果你将放大行为拉出来,就可以更好地控制它。例如,不是

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

尝试:

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

然后您可以直接设置缩放级别和翻译:

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

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

限制平移范围有点棘手。在缩放功能中,如果平移或比例不合适,则可以不进行更新(或将缩放的“平移”设置为所需值)。类似于以下内容(以你的情况为例):

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

记住,如果您想要缩放允许轴上的负值,但不允许平移,您会发现自己陷入一些棘手的情况。
这可能已经过时了,但请查看在D3.js中缩放或平移时限制域名 还要注意,缩放行为确实具有限制平移和缩放到一个点的功能。 但是该代码在后来的更新中被删除了。

再次感谢Superboggly!您的zoomIn/reset代码似乎可行,但是比例只会在下一次缩放事件(即拖动或滚轮)上更改。我很难让事情在按钮单击时更新。这一定很简单,但是我就是想不出来。我的按钮单击代码如下: d3.select('#zoomIn').call(zoom).on('click',function(){ d3.event.preventDefault(); zm.scale(zm.scale()*2); });我尝试了各种使用.call()、zoom()和.on()的方法,但都没有成功。 - richardwestenra
我在我的上一个jsfiddle中为您添加了一个快速的蓝色缩放方块。你所缺少的只是在设置比例之后调用zoom()函数。可以将提供的zoom函数视为应用行为计算出的缩放状态。 - Superboggly
好的!已解决!原来我一直在做这件事,但它对我不起作用...长话短说:你使用的是d3版本3,而我使用的是版本2,这就是为什么当我把你的蓝色正方形放到我的代码中时它不起作用的原因。现在已经升级到v3,它可以工作了。干杯 :) - richardwestenra
啊,好的,知道了!我知道他们重写了缩放行为,但我没有想到要检查版本! - Superboggly
没问题:)。顺便说一下,我已经基本上完成了它的运输,但是以后参考(万一有人偶然发现这个页面并想知道我是如何做到的或者能提出改进意见):我在计算翻译函数时遇到了一些麻烦,所以最终我只好插入了一些任意常数。它似乎足够好用: 函数 trans(xy,constant){ return zm.translate()[xy]+(constant*(zm.scale())); } zm.translate([trans(0,-10),trans(1,-350)]); - richardwestenra

-1

我不喜欢重复造轮子。我在寻找可以进行缩放的散点图。Highcharts是其中之一,但还有基于D3的plotly,它不仅允许缩放,还可以在散点图上添加线性数据集,这正是我希望在某些数据集中实现的功能,在其他绘图库中很难找到。我想试试看:

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

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

使用这样好的库可以节省您很多时间和痛苦。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接