2015-09-20 62 views
0

學習D3我創建了一個基於此example的圖表。該圖表以JS封閉形式實施,其中D3中的Mike Bostock的convention for creating reusable components。 (或儘可能接近我)縮放和平移D3圖表時的幻影線路徑

縮放和平移時,行路徑不能正確重畫。

在我的圖表中,我有散點圖和連接點的線路。點工作,但不是線。它可能(或許)與在縮放行爲期間重新綁定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) 
 
\t \t \t \t    .attr("cx", function (d) { return xScale(d.dt); }) 
 
\t \t \t \t    .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)) 
 
\t .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]); 
 

 
    }})()
#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; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> 
 

 
    <div id="chart"></div>

我怎麼行正確重繪?

回答

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

我相信有這樣做的更好的方式,但我認爲這將讓你開始。

希望它有幫助。

+0

謝謝。我不太確定你有沒有比刪除'幽靈'更好的方式!......,這正是需要的。 – Kickaha