我正在制作D3时标/价格表金融图表。图表SVG本身使用zoom()来几何平移和缩放数据并重新绘制轴。图表下方是一个SVG画笔窗格,它以高级别显示整个数据集,并允许自行平移。我面临的问题与此小提琴中显示的行为相同(不是我的代码):http://jsfiddle.net/p29qC/8/。刷牙后缩放会导致跳跃行为,因为zoom()从未从brush()中获取更改。
缩放和刷子可以独立工作,但我无法让它们协同工作。当图表被刷过时,我希望缩放能够检测到这一点,所以下次图表缩放时,它会在画笔停止的地方拾取。反之亦然。
我已设法设置一个同步功能,以便在启动缩放时让画笔正确更新,但我无法反向工作 - 在导航器中进行画笔时更新图表缩放。我找了好几个小时无济于事。有什么补丁可以解决这个问题吗?我为长代码块道歉,但我希望设置上下文有帮助!
设置代码(为简洁起见省略了一些基本变量):
// Create svg
var svg = d3.select('#chart')
.append('svg')
.attr({
class: 'fcChartArea',
width: width+margin.left+margin.right,
height: height+margin.bottom,
})
.style({'margin-top': margin.top});
// Create group for the chart
var chart = svg.append('g');
// Clipping path
chart.append('defs').append('clipPath')
.attr('id', 'plotAreaClip')
.append('rect')
.attr({
width: width,
height: height
});
// Create plot area, using the clipping path
var plotArea = chart.append('g')
.attr({
class: 'plotArea',
'clip-path': 'url(#plotAreaClip)'
});
// Compute mins and maxes
var minX = d3.min(data, function (d) {
return new Date(d.startTime*1000);
});
var maxX = d3.max(data, function (d) {
return new Date(d.startTime*1000);
});
var minY = d3.min(data, function (d) {
return d.low;
});
var maxY = d3.max(data, function (d) {
return d.high;
});
// Compute scales & axes
var dateScale = d3.time.scale()
.domain([minX, maxX])
.range([0, width]);
var dateAxis = d3.svg.axis()
.scale(dateScale)
.orient('bottom');
var priceScale = d3.scale.linear()
.domain([minY, maxY])
.nice()
.range([height, 0]);
var priceAxis = d3.svg.axis()
.scale(priceScale)
.orient('right');
// Store initial scales
var initialXScale = dateScale.copy();
var initialYScale = priceScale.copy();
// Add axes to the chart
chart.append('g')
.attr('class', 'axis date')
.attr('transform', 'translate(0,' + height + ')')
.call(dateAxis);
chart.append('g')
.attr('class', 'axis price')
.attr('transform', 'translate(' + width + ',0)')
.call(priceAxis);
// Compute and append the OHLC series
var series = fc.series.ohlc('path')
.xScale(dateScale)
.yScale(priceScale);
var dataSeries = plotArea.append('g')
.attr('class', 'series')
.datum(data)
.call(series);
// Create the SVG navigator
var navChart = d3.select('#chart')
.classed('chart', true)
.append('svg')
.classed('navigator', true)
.attr('width', navWidth + margin.left + margin.right)
.attr('height', navHeight+margin.top+margin.bottom)
.style({'margin-bottom': margin.bottom})
.append('g');
// Compute scales & axes
var navXScale = d3.time.scale()
.domain([minX, maxX])
.range([0, navWidth]);
var navXAxis = d3.svg.axis()
.scale(navXScale)
.orient('bottom');
var navYScale = d3.scale.linear()
.domain([minY, maxY])
.range([navHeight, 0]);
// Add x-axis to the chart
navChart.append('g')
.attr('class', 'axis date')
.attr('transform', 'translate(0,' + navHeight + ')')
.call(navXAxis);
// Add data to the navigator
var navData = d3.svg.area()
.x(function (d) {
return navXScale(new Date(d.startTime*1000));
})
.y0(navHeight)
.y1(function (d) {
return navYScale(d.close);
});
var navLine = d3.svg.line()
.x(function (d) {
return navXScale(new Date(d.startTime*1000));
})
.y(function (d) {
return navYScale(d.close);
});
navChart.append('path')
.attr('class', 'data')
.attr('d', navData(data));
navChart.append('path')
.attr('class', 'line')
.attr('d', navLine(data));
// create brush viewport
var viewport = d3.svg.brush()
.x(navXScale)
.on("brush", brush);
// add brush viewport to the SVG navigator
navChart.append("g")
.attr("class", "viewport")
.call(viewport)
.selectAll("rect")
.attr("height", navHeight);
// set zoom behavior
var zoom = d3.behavior.zoom()
.x(dateScale)
.scaleExtent([1, 12.99])
.on('zoom', zoom);
// Create zoom pane
plotArea.append('rect')
.attr('class', 'zoom-overlay')
.attr('width', width)
.attr('height', height)
.call(zoom);
画笔和缩放功能:
// zoom - brush synchronizations
function updateBrushFromZoom() {
if ((dateScale.domain()[0] <= minX) && (dateScale.domain()[1] >= maxX)) {
viewport.clear();
} else {
viewport.extent(dateScale.domain());
}
navChart.select('.viewport').call(viewport);
}
function updateZoomFromBrush() {
// help!!
}
function brush() {
var g = d3.selectAll('svg').select('g');
var newDomain = viewport.extent();
if (newDomain[0].getTime() !== newDomain[1].getTime()) {
dateScale.domain([newDomain[0], newDomain[1]]);
var xTransform = fc.utilities.xScaleTransform(initialXScale, dateScale);
// define new data set
var range = moment().range(newDomain[0], newDomain[1]);
var rangeData = [];
for (var i = 0; i < data.length; i += 1) {
if (range.contains(new Date(data[i].startTime*1000))) {
rangeData.push(data[i]);
}
}
// define new mins and maxes
var newMinY = d3.min(rangeData, function (d) {
return d.low;
});
var newMaxY = d3.max(rangeData, function (d) {
return d.high;
});
// set new yScale
priceScale.domain([newMinY, newMaxY]);
var yTransform = fc.utilities.yScaleTransform(initialYScale, priceScale);
// draw new axes on main chart
g.select('.fcChartArea .date.axis')
.call(dateAxis);
g.select('.fcChartArea .price.axis')
.call(priceAxis);
// transform the data to fit new chart viewport
g.select('.series')
.attr('transform', 'translate(' + xTransform.translate + ',' + yTransform.translate+ ')' + ' scale(' + xTransform.scale + ',' + yTransform.scale + ')');
}
else {
// remove transformation
g.select('.series')
.attr('transform', null);
}
updateZoomFromBrush();
}
// Zoom functions
function zoom() {
var g = d3.selectAll('svg').select('g');
// set new xScale
var newDomain = dateScale.domain();
var xTransformTranslate = d3.event.translate[0];
var xTransformScale = d3.event.scale;
// define new data set
var range = moment().range(newDomain[0], newDomain[1]);
var rangeData = [];
for (var i = 0; i < data.length; i += 1) {
if (range.contains(new Date(data[i].startTime*1000))) {
rangeData.push(data[i]);
}
}
// define new max and min
var newMinY = d3.min(rangeData, function (d) {
return d.low;
});
var newMaxY = d3.max(rangeData, function (d) {
return d.high;
});
// set new yScale
priceScale.domain([newMinY, newMaxY]);
var yTransform = fc.utilities.yScaleTransform(initialYScale, priceScale);
// draw new axes on main chart
g.select('.fcChartArea .date.axis')
.call(dateAxis);
g.select('.fcChartArea .price.axis')
.call(priceAxis);
// transform the data to fit new chart viewport
g.select('.series')
.attr('transform', 'translate(' + xTransformTranslate + ',' + yTransform.translate+ ')' + ' scale(' + xTransformScale + ',' + yTransform.scale + ')');
// update SVG navigator
updateBrushFromZoom();
}
助手功能:
fc.utilities.yScaleTransform = function(oldScale, newScale) {
var oldDomain = oldScale.domain();
var newDomain = newScale.domain();
var scale = (oldDomain[1] - oldDomain[0]) / (newDomain[1] - newDomain[0]);
var translate = scale * (oldScale.range()[1] - oldScale(newDomain[1]));
return {
translate: translate,
scale: scale
};
};
fc.utilities.xScaleTransform = function(oldScale, newScale) {
var oldDomain = oldScale.domain();
var newDomain = newScale.domain();
var scale = (oldDomain[1] - oldDomain[0]) / (newDomain[1] - newDomain[0]);
var translate = scale * (oldScale.range()[0] - oldScale(newDomain[0]));
return {
translate: translate,
scale: scale
};
};
答案 0 :(得分:3)
在updateZoomFromBrush()
中,使用zoom.x(dateScale)
将缩放重新绑定到缩放行为。
这是必需的,因为d3.behavior.zoom()
对您传入的比例的副本进行操作,因此如果不重新调整比例,行为将不会对比例域进行任何更改在brush()
。