简体   繁体   English

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

[英]Ghost line path when zooming and panning a D3 chart

Learning D3 I created a chart based off this example . 学习D3我根据此示例创建了一个图表。 The chart is implemented as a JS closure, with Mike Bostock's convention for creating reusable components in D3. 该图表以JS闭包形式实现,这与Mike Bostock的约定有关在D3中创建可重用组件约定有关 (or as close as I can get) (或尽可能接近)

When zooming and panning, the line path is not redrawn correctly. 缩放和平移时,无法正确重绘线路径。

In my chart I have a scatter plot and a line path joining the dots. 在我的图表中,我有一个散点图和一个连接点的直线路径。 The dots work but not the line. 点起作用,但线不起作用。 It's (maybe) something to do with rebinding the xScale during the onzoom behavior.... I've tried exposing the line function / object and a bunch of trial and error stuff but am at my wits end. (也许)与在onzoom行为期间重新绑定xScale有关。...我曾尝试公开行函数/对象以及大量的试验和错误的东西,但我不知所措。 Any help much appreciated. 任何帮助,不胜感激。

Please see this codepen, or run the embedded code snippet. 请参阅此代码笔,或运行嵌入式代码段。

http://codepen.io/Kickaha/pen/epzNyw 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 xy 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]); }})() 
 #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> 

How do I get the line to redraw correctly? 如何获得线条以正确重绘?

Thank you for a very well documented question. 谢谢您提出了一个非常有据可查的问题。

What you are doing, is that on zoom you re-draw the line, without removing the one already existing in your SVG element. 您正在做的是在缩放时重新绘制线条,而不会删除SVG元素中已经存在的那条。 May I suggest the following: 我可以提出以下建议:

Change your zoom method to: 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();            
    });

I am sure there are better ways of doing it, but I think this will get you started. 我相信有更好的方法可以这样做,但是我认为这可以帮助您入门。

Hope it helps. 希望能帮助到你。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM