D3缩放到刷子范围

3
我正在开发一个网络工具,该工具可以使用给定的数据创建散点图。由于我对可视化一无所知(而且也不是JavaScript专家),因此我决定使用D3。
通过跟随教程、观察示例和尝试实现,我已经成功创建了带有刷选功能的散点图(目前并没有实现任何功能)。由于我的目标是绘制基因,其中许多基因在x和y坐标方面非常接近。因此,我想要实现类似这样的东西,但在x和y方向上都要实现。基本上就是在刷选的区域内缩放。
到目前为止,我还无法创建这种行为,此时我的轴确实发生了变化,但不再显示值,也没有任何缩放发生。您可以在我的玩具子域上实时查看。我不确定哪里出错了,因此需要任何帮助。
玩具子域上使用的JavaScript:
// General variables

var svg, width, height;
var padding = 30;

var dataset = [
              [ 5,     20 ],
              [ 480,   90 ],
              [ 250,   50 ],
              [ 100,   33 ],
              [ 330,   95 ],
              [ 410,   12 ],
              [ 475,   44 ],
              [ 25,    67 ],
              [ 85,    21 ],
              [ 220,   88 ]
          ];

function drawGraph(){
    scatterplot_area_size = $("#scatterplot").width();
    width = scatterplot_area_size;
    height = scatterplot_area_size * 0.75;

    console.log(width);

    // Retrieve the interval of the x and y data
    var maxX = d3.max(dataset, function(d) {
        return d[0];  //References first value in each sub-array
    });

    var minX = d3.min(dataset, function(d) {
        return d[0];  //References first value in each sub-array
    });

    var maxY = d3.max(dataset, function(d) {
        return d[1];  //References second value in each sub-array
    });

    var minY = d3.min(dataset, function(d) {
        return d[1];  //References second value in each sub-array
    });

    // Create a (square) scatterplot area in the div with id scatterplot
    // which spans the entire div in width
    svg = d3.select("#scatterplot")
            .append("svg")
            .attr("width", width)
            .attr("height", height);

    // plot all points
    var points = svg.append("g")
        .attr("class", "point")
        .selectAll("point")
        .data(dataset)
        .enter()
        .append("circle");


    console.log(minX + " " + minY);

    // Create x and y scales
    var x = d3.scale.linear()
                     .domain([minX, maxX])
                     .range([padding, (width-padding)]);

    var y = d3.scale.linear()
                     .domain([minY, maxY])
                     .range([(height-padding), padding]); // Reverse the scale to let high values show at the top and low values at the bottom

    // Set the x and y positions as well as the radius (hard coded 5)
    points.attr("cx", function(d) {
        return x(d[0]);
    })
   .attr("cy", function(d) {
        return y(d[1]);
    })
    .attr("r", 5);

    // Create the x and y axes
    var xAxis = d3.svg.axis()
                  .scale(x)
                  .ticks(10)
                  .orient("bottom");

    var yAxis = d3.svg.axis()
                  .scale(y)
                  .ticks(10)
                  .orient("left");

    // Add the axes to the scatterplot
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + (height-padding) + ")")
        .call(xAxis);

    svg.append("g")
        .attr("class", "y axis")
        .attr("transform", "translate(" + padding + ",0)")
        .call(yAxis);

    // Allow for brushing (selection of points)
    var brush = d3.svg.brush()
        .x(x)
        .y(y)
        .on("brush", brushmove)
        .on("brushend", brushend);

    svg.append("g")
        .attr("class", "brush")
        .call(brush)
        .selectAll('rect')
        .attr('height', height);

    function brushmove() {
      var extent = brush.extent();
      points.classed("selected", function(d) {
        return extent[0][0] <= d[0] && d[0] <= extent[1][0]
                && extent[0][1] <= d[1] && d[1] <= extent[1][1];
      });
    }

    function brushend() {
        x.domain(brush.extent());
        y.domain(brush.extent());

        transition_data();
        reset_axis();

        points.classed("selected", false);
        d3.select(".brush").call(brush.clear());
    }

    function transition_data() {
        console.log("transistion data called");
        d3.selectAll("point")
        .transition()
        .duration(500)
        .attr("cx", function(d) { console.log(d[0]); return x(d[0]); })
        .attr("cy", function(d) { return y(d[1]); });
    }

    function reset_axis() {
        svg.transition().duration(500)
        .select(".x.axis")
        .call(xAxis);

        svg.transition().duration(500)
        .select(".y.axis")
        .call(yAxis);
    }
}

// Wait until the document is loaded, then draw the graph.
$( document ).ready(function() {
    drawGraph();
});

// If the window is resized, redraw graph to make it fit again.
$( window ).resize(function() {
    if(typeof svg != 'undefined'){
        $("#scatterplot").empty();
    } 
    drawGraph();
});

你有收到任何错误信息吗? - Lars Kotthoff
不,我的控制台没有显示任何错误或警告。 - Gooey
1个回答

5

你的代码中有两个错误:

  1. In your function brushend() you are setting the domains of your scales using the return value of brush.extent() in both cases. Notice, however, that the extent returned by your brush is a two-dimensional array like [[xMin,yMin],[xMax,yMax]]. You have to set your domains using

    var extent = brush.extent();
    x.domain([extent[0][0],extent[1][0]]);  // x.domain([xMin,xMax])
    y.domain([extent[0][1],extent[1][1]]);  // y.domain([yMin,yMax])
    

    These adjustments will have your axes scale correctly while the points are still fixed.

  2. You are inserting and manipulating your data points with d3.selectAll("point") which refers to them as being of type svg:point. However, there is no such type of svg element. Though your call to d3.selectAll("point") is still valid it will always yield an empty selection because there is no element to be selected. For this reason your first call to the function when setting up the svg does work, since it will put all the data bound to the enter selection giving the desired result. For the sake of correctness you should correct this to

    // plot all points
    var points = svg.append("g")
        .attr("class", "point")
        // .selectAll("point")
        .selectAll("circle")    // <-- This will work
        .data(dataset)
        .enter()
        .append("circle");
    

    When trying to update your points after having set the scales' domains the faulty code will not work because in this case the empty selection is meaningless and will have no effect on any element. Since in the above quoted statement you have already stored a reference to your selection in var points there is no need to select them again. You could just rewrite your update as follows:

    function transition_data() {
        // d3.selectAll("point")   // Instead of this...
        points                     // <-- ...use the reference to your selection
            .transition()
            .duration(500)
            .attr("cx", function(d) { return x(d[0]); })
            .attr("cy", function(d) { return y(d[1]); });
    }
    

我制作了一个可行的JSFiddle来展示解决方案。


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