学习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;
如何让线条正确重绘?
答案 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();
});
我确信有更好的方法可以做到这一点,但我认为这会让你开始。
希望它有所帮助。