缩放和平移D3图表时的Ghost线路径

时间:2015-09-20 05:12:41

标签: javascript d3.js

学习D3我基于此example创建了一个图表。该图表是作为JS闭包实现的,Mike中有Mike Bostock的convention for creating reusable components。 (或尽可能接近)

缩放和平移时,不会正确重绘线路径。

在我的图表中,我有一个散点图和一个连接点的线路。点工作但不是线。它(可能)与在onzoom行为期间重新绑定xScale有关。我已经尝试过暴露行函数/对象和一堆试验和错误的东西,但我的智慧结束了。任何帮助非常感谢。

请参阅此codepen,或运行嵌入式代码段。

http://codepen.io/Kickaha/pen/epzNyw



var MyNS = MyNS || {};

MyNS.EvalChartSeries = function () {
    
    var xScale = d3.time.scale(),
        yScale = d3.scale.linear();
         //I tried exposing the line function / object to be able to call it in the on zoom ... no dice.  
        //var line = d3.svg.line();
    
    var EvalChartSeries = function (selection) {
        
        selection.each(function (dataIn) {
            //select and bind data for scatter dots
            spots = d3.select(this).selectAll("circle")
                .data(dataIn);             
            //enter and create a circle for any unassigned datum
            spots.enter().append("circle");
            
            //update the bound items using the x-y scale function to recalculate 
            spots
                            .attr("r", 8)
				            .attr("cx", function (d) { return xScale(d.dt); })
				            .attr("cy", function (d) { return yScale(d.spot); })
                            .style("fill", function (d) {
                switch (d.eva) {
                    case 1: return "green"; break;
                    case 2: return "red"; break;
                    case 3: return "blue"; break;
                    case 4: return "yellow"; break;}
            });
            
           //exit to remove any unused data, most likely not needed in this case as the data set is static
           spots.exit().remove();           
          
          //here the line function/object is assigned it's scale and bound to data
           var line = d3.svg.line().x(function (d) { return xScale(d.dt); })
                .y(function (d) { return yScale(d.spot); }).interpolate("linear");
          
          //and here is where the line is drawn by appending a set of svg path points
          //, it does not use the select, enter, update, exit logic because a line while being a set of points is one thing               (http://stackoverflow.com/questions/22508133/d3-line-chart-with-enter-update-exit-logic)
                   
            lines = d3.select(this)
            .append("path");
            
            lines
            .attr('class', 'line')
            .attr("d", line(dataIn))
            .attr("stroke", "steelblue").attr("stroke-width", 1);
        });

    };
    
    //The scales are exposed as properties, and they return the object to support chaining
    EvalChartSeries.xScale = function (value) {
        if (!arguments.length) {
            return xScale;
        }
        xScale = value;
        return EvalChartSeries;
    };
    
    EvalChartSeries.yScale = function (value) {
        if (!arguments.length) {
            return yScale;
        }
        yScale = value;
        return EvalChartSeries;
    };

  /*
  Here I tried to expose the line function/object as a property to rebind it to the xAxis when redrawing ... didnt work 
    EvalChartSeries.line = function (value) {
        if (!arguments.length) {
            return line;
        }
        line = value;
        //linePath.x = function (d) { return xScale(d.dt); };
        return EvalChartSeries;
    };*/
  
    //the function returns itself to suppport method chaining  
    return EvalChartSeries;

};

//The chart is defined here as a closure to enable Object Orientated reuse (encapsualtion / data hiding etc.. )
MyNS.DotsChart = (function () {

  data = [{"dt":1280780384000,"spot":1.3173999786376953,"eva":4},
{"dt":1280782184000,"spot":1.3166999816894531,"eva":4},
{"dt":1280783084000,"spot":1.3164000511169434,"eva":4},
{"dt":1280781284000,"spot":1.3167999982833862,"eva":4},
{"dt":1280784884000,"spot":1.3162000179290771,"eva":4},
{"dt":1280783984000,"spot":1.3163000345230103,"eva":4},
{"dt":1280785784000,"spot":1.315999984741211,"eva":4},
{"dt":1280786684000,"spot":1.3163000345230103,"eva":4},
{"dt":1280787584000,"spot":1.316100001335144,"eva":4},
{"dt":1280788484000,"spot":1.3162000179290771,"eva":4},
{"dt":1280789384000,"spot":1.3164000511169434,"eva":4},
{"dt":1280790284000,"spot":1.3164000511169434,"eva":4},
{"dt":1280791184000,"spot":1.3166999816894531,"eva":4},
{"dt":1280792084000,"spot":1.3169000148773193,"eva":4},
{"dt":1280792984000,"spot":1.3170000314712524,"eva":4},
{"dt":1280793884000,"spot":1.3174999952316284,"eva":4},
{"dt":1280794784000,"spot":1.3171000480651855,"eva":4},
{"dt":1280795684000,"spot":1.3163000345230103,"eva":2},
{"dt":1280796584000,"spot":1.315600037574768,"eva":2},
{"dt":1280797484000,"spot":1.3154000043869019,"eva":2},
{"dt":1280798384000,"spot":1.3147000074386597,"eva":2},
{"dt":1280799284000,"spot":1.3164000511169434,"eva":2},
{"dt":1280800184000,"spot":1.3178000450134277,"eva":4},
{"dt":1280801084000,"spot":1.3176000118255615,"eva":4},
{"dt":1280801984000,"spot":1.3174999952316284,"eva":4},
{"dt":1280802884000,"spot":1.3193000555038452,"eva":3},
{"dt":1280803784000,"spot":1.32260000705719,"eva":4},
{"dt":1280804684000,"spot":1.3216999769210815,"eva":4},
{"dt":1280805584000,"spot":1.3233000040054321,"eva":4},
{"dt":1280806484000,"spot":1.3229000568389893,"eva":4},
{"dt":1280807384000,"spot":1.3229999542236328,"eva":2},
{"dt":1280808284000,"spot":1.3220000267028809,"eva":2},
{"dt":1280809184000,"spot":1.3224999904632568,"eva":2},
{"dt":1280810084000,"spot":1.3233000040054321,"eva":2},
{"dt":1280810984000,"spot":1.3240000009536743,"eva":2},
{"dt":1280811884000,"spot":1.3250000476837158,"eva":4},
{"dt":1280812784000,"spot":1.3253999948501587,"eva":4},
{"dt":1280813684000,"spot":1.3248000144958496,"eva":4},
{"dt":1280814584000,"spot":1.3250000476837158,"eva":4},
{"dt":1280815484000,"spot":1.3249000310897827,"eva":4},
{"dt":1280816384000,"spot":1.3238999843597412,"eva":2},
{"dt":1280817284000,"spot":1.3238999843597412,"eva":2},
{"dt":1280818184000,"spot":1.322700023651123,"eva":2},
{"dt":1280819084000,"spot":1.32260000705719,"eva":2},
{"dt":1280819984000,"spot":1.3219000101089478,"eva":2},
{"dt":1280820884000,"spot":1.323199987411499,"eva":4},
{"dt":1280821784000,"spot":1.3236000537872314,"eva":4},
{"dt":1280822684000,"spot":1.3228000402450562,"eva":4},
{"dt":1280823584000,"spot":1.3213000297546387,"eva":2},
{"dt":1280824484000,"spot":1.3214999437332153,"eva":2},
{"dt":1280825384000,"spot":1.3215999603271484,"eva":2},
{"dt":1280826284000,"spot":1.320199966430664,"eva":2},
{"dt":1280827184000,"spot":1.3187999725341797,"eva":2},
{"dt":1280828084000,"spot":1.3200000524520874,"eva":2},
{"dt":1280828984000,"spot":1.3207000494003296,"eva":1}
  ];
  
  
   var minDate = d3.min(data, function (d) { return d.dt; }),
   maxDate = d3.max(data, function (d) { return d.dt; });    
   var yMin = d3.min(data, function (d) { return d.spot; }),
   yMax = d3.max(data, function (d) { return d.spot; });

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Set up the drawing area
    
    var margin = {top: 20, right: 20, bottom: 30, left: 35},
        width = 1600 - margin.left - margin.right,
        height = 400 - margin.top - margin.bottom;    

    //select the single element chart in the html body (this is expected to exist) and append a svg element
    var plotChart =d3.select('#chart')
    .append("svg:svg")
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .append('svg:g')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

    var plotArea = plotChart.append('g')
        .attr('clip-path', 'url(#plotAreaClip)');//http://stackoverflow.com/questions/940451/using-relative-url-in-css-file-what-location-is-it-relative-to
    
    plotArea.append('clipPath')
        .attr('id', 'plotAreaClip')
        .append('rect')
        .attr({ width: width, height: height });

    // Scales
    var xScale = d3.time.scale(),
        yScale = d3.scale.linear();

    // Set scale domains
    xScale.domain([minDate, maxDate]);
    yScale.domain([yMin, yMax]).nice();

    // Set scale ranges
    xScale.range([0, width]);
    yScale.range([height, 0]);

    // Axes
    var xAxis = d3.svg.axis()
        .scale(xScale)
        .orient('bottom')
        .ticks(5);

    var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient('left');
    
    
  /*  var line = d3.svg.line()
                .x(function (d) { return xScale(d.dt); })
                .y(function (d) { return yScale(d.spot); }).interpolate("linear");
    */

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

    plotChart.append('g')
        .attr('class', 'y axis')
        .call(yAxis);

    // Data series
    var series = MyNS.EvalChartSeries()
        .xScale(xScale)
        .yScale(yScale);
       // .line(line); exposing this property did nothing 
        
    //appending a group 'g' tag binding the data and calling on our d3 line+dots chart object to process it    
    var dataSeries = plotArea.append('g')
        .attr('class', 'series')
        .datum(data)
        .call(series);


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Zooming and panning
    //on zoom check extents , then most importantny redraw the chart
    var zoom = d3.behavior.zoom()
        .x(xScale)
        .on('zoom', function() {
            if (xScale.domain()[0] < minDate) {
                zoom.translate([zoom.translate()[0] - xScale(minDate) + xScale.range()[0], 0]);
            } else if (xScale.domain()[1] > maxDate) {
                zoom.translate([zoom.translate()[0] - xScale(maxDate) + xScale.range()[1], 0]);
            }
            //most important to redraw "on zoom" 
            redrawChart();            
        });

    //an overlay area to catch mouse events from the full area of the chart (not just the rendered dots and line)
    var overlay = d3.svg.area()
        .x(function (d) { return xScale(d.dt); })
        .y0(0)
        .y1(height);
    //an area is a path object, not to be confused with our line path 
    plotArea.append('path')
        .attr('class', 'overlay')
        .attr('d', overlay(data))
	.call(zoom);

    redrawChart();    
    updateZoomFromChart();

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Helper methods

    function redrawChart() {
        //redraws the scatter data series
        dataSeries.call(series);
        //redraws the xaxis to show the current zoom pan area
        plotChart.select('.x.axis').call(xAxis);
        
     //   plotChart.select(".line")
      //  .attr("class", "line");
      //  .attr("d", line);

     
    //filters the data set to what is visible given teh current zoom pan state
      var yExtent = d3.extent(data.filter(function (d) {
            var dt = xScale(d.dt);
            return dt > 0 && dt < width;
        }), function (d) { return d.spot; });
        
        yScale.domain(yExtent).nice();        
      //this scales the y axis to maximum visibility as the line is zoomed and panned
        plotChart.select(".y.axis").call(yAxis);
    }       
   //takes care of zooming and panning past the ends of the data.
    function updateZoomFromChart() {
        var fullXDomain = maxDate - minDate,
            currentXDomain = xScale.domain()[1] - xScale.domain()[0];
        var minXScale = currentXDomain / fullXDomain,
            maxXScale = minXScale * 20;
        zoom.x(xScale)
            .scaleExtent([minXScale, maxXScale]);

    }})()
&#13;
#chart {
    margin-top: 20px;
    margin-bottom: 20px;
    width: 660px;
}.chart .overlay {             
    stroke-width: 0px;
    fill-opacity: 0;
}
.overlay {             
    stroke-width: 0px;
    fill-opacity: 0;
}


body {
  padding: 10px 20px;
  background: #ffeeee;
  font-family: sans-serif;
  text-align: center;
  color: #7f7;
}.line {
    fill: none;
    stroke: steelblue;
    stroke-width: 1.5px;
}

.axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}

.axis text {
    font-family: sans-serif;
    font-size: 10px;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

  <div id="chart"></div>
&#13;
&#13;
&#13;

如何让线条正确重绘?

1 个答案:

答案 0 :(得分:1)

感谢您提供一个记录完备的问题。

您正在做的是,在缩放时重新绘制线条,而不删除SVG元素中已存在的线条。我可以建议如下:

zoom方法更改为:

var zoom = d3.behavior.zoom()
    .x(xScale)
    .on('zoom', function() {
        if (xScale.domain()[0] < minDate) {
            zoom.translate([zoom.translate()[0] - xScale(minDate) + xScale.range()[0], 0]);
        } else if (xScale.domain()[1] > maxDate) {
            zoom.translate([zoom.translate()[0] - xScale(maxDate) + xScale.range()[1], 0]);
        }
      // add the following line, to remove the lines already present
      d3.selectAll('.line').remove()
        //most important to redraw "on zoom" 
        redrawChart();            
    });

我确信有更好的方法可以做到这一点,但我认为这会让你开始。

希望它有所帮助。