【问题标题】:d3.js scatter plot - zoom/drag boundaries, zoom buttons, reset zoom, calculate mediand3.js 散点图 - 缩放/拖动边界、缩放按钮、重置缩放、计算中位数
【发布时间】:2013-02-10 18:33:19
【问题描述】:

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

有几个我一直无法弄清楚的功能,如果有人能指出我正确的方向,我会很乐意帮忙:

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

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

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

下面是一个非常简化(即旧)的 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]]);
        }
        ...
    };

我使用的缩放平移是非常临时的,基本上使用任意常量来或多或少地保持定位在正确的位置。这并不理想,我愿意接受有关更普遍合理的技术的建议。但是,它在这种情况下运行良好。

【问题讨论】:

    标签: javascript d3.js zooming drag scatter-plot


    【解决方案1】:

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

    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())]);
        }
        ....
    }        
    

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

    这可能过时了,但请查看Limiting domain when zooming or panning in D3.js

    另请注意,缩放行为确实具有在one point 处限制平移和缩放的功能。但是代码在后来的update中被取出来了。

    【讨论】:

    • 再次感谢 Superboggly!您的 zoomIn/reset 代码似乎有效,但比例仅在下一个缩放事件(即拖动或鼠标滚轮)时发生变化。我很难在按钮单击时更新内容。它一定很简单,但我就是想不通。我的按钮点击代码是: d3.select('#zoomIn').call(zoom).on('click',function(){ d3.event.preventDefault(); zm.scale(zm.scale()*2 ); });我尝试了 .call()、zoom() 和 .on() 的各种用法,但均无济于事。
    • 我在last jsfiddle for you 的底部添加了一个快速的蓝色缩放方块。您所缺少的只是在设置比例后调用 zoom() 。将您提供的缩放功能视为应用由行为计算的缩放状态。
    • 好的!排序!事实证明,这正是我正在做的,但它对我不起作用......长话短说:当我使用版本 2 时,你使用的是 d3 版本 3,这就是为什么当我放弃你的时候它不起作用的原因我的代码中的蓝色方块。已升级到 v3,现在可以使用。干杯:)
    • 啊,很高兴知道!我知道他们重写了缩放行为,但我没想过要检查版本!
    • 不用担心 :)。顺便说一句,我现在几乎已经发布了它,但为了将来参考(以防有人偶然发现这个页面并想知道我是如何做到的或者可以提出改进建议):我在计算翻译函数时遇到了一些麻烦,所以我最终敲入了一些任意常数。它似乎工作得很好: function trans(xy,constant){ return zm.translate()[xy]+(constant*(zm.scale())); } zm.translate([trans(0,-10),trans(1,-350)]);
    【解决方案2】:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 2016-02-27
      • 2018-03-03
      • 2015-06-21
      • 2023-03-25
      • 2016-01-10
      相关资源
      最近更新 更多