d3.js v4 小地图或如何在两个元素上共享缩放?
d3.js v4 minimap or how to have shared zoom on two elements?
我找到了zooming with minimap这个很好的例子,但是它是用旧版本v3写的。我差点把它转换成 v4,但是 d3.event
有问题。在 v3 d3.event
中,似乎在调用缩放的两个元素之间共享缩放参数。因此,如果我在主 canvas 上缩放,然后在小地图 canvas 上缩放 - d3.event 将具有主 canvas 缩放 d3.event
和它将继续按原样缩放。但是在 v4 中,两个 d3 缩放事件都以某种方式具有单独的缩放或平移值。如文档中所示:
The zoom behavior stores the zoom state on the element to which the zoom behavior was applied, not on the zoom behavior itself. This is because the zoom behavior can be applied to many elements simultaneously, and each element can be zoomed independently.
但这留下了一个问题,我如何才能在两个元素上共享一个缩放事件?
编辑:
也非常感谢 Bill White,他在同一个主题中发布了他更新的文章 - D3 MINIMAP V4 UPDATE。这篇文章实际上是我在问题中发布的示例的更新。
可以找到问题的答案 here。
@mbostock 在问题线程中亲切地回答了我的问题。我还找到了另一个解决方案。我成功地将他的代码中的一些逻辑和公式直接实现到我的缩放处理程序中。它就像一个魅力。
所以这是从 v3 到 v4 的更新缩放处理程序:
注意:不包括翻译边界检查。
// ....
var scale = 1;
var minScale = .5;
var maxScale = 7.5;
var translation = [0, 0];
// ....
// Used for both main canvas and minimap zoom
var zoom = d3.zoom()
.on('zoom', zoomHandler);
function zoomHandler(newScale) {
var prevScale = scale;
var previousTranslation = getXYFromTranslate(panCanvas.attr('transform'));
var isZoomEvent = d3.event && d3.event.sourceEvent.deltaY;
var isDragEvent = d3.event && (d3.event.sourceEvent.movementX || d3.event.sourceEvent.movementY);
if (isZoomEvent) {
scale = calculateNewScale(prevScale, d3.event.sourceEvent);
scale = checkScaleBounderies(scale);
var mousePosition = d3.mouse(this);
// Based on d3.js zoom algorythm
translation[0] = mousePosition[0] - ((mousePosition[0] - previousTranslation[0]) / prevScale) * scale;
translation[1] = mousePosition[1] - ((mousePosition[1] - previousTranslation[1]) / prevScale) * scale;
} else if (isDragEvent) {
translation[0] = previousTranslation[0] + d3.event.sourceEvent.movementX;
translation[1] = previousTranslation[1] + d3.event.sourceEvent.movementY;
} else if (newScale) {
scale = newScale;
}
// Apply the new dimensions to the main canvas
panCanvas.attr('transform', 'translate(' + translation + ') scale(' + scale + ')');
// Apply the new dimensions to the minimap
minimap.scale(scale).render();
}
// Calculate the new scale value based on d3.js zoom formula
function calculateNewScale(prevScale, event) {
return prevScale * Math.pow(2, -event.deltaY * (event.deltaMode ? 120 : 1) / 500);
}
// Check if scale has reached max or min
function checkScaleBounderies(newScale) {
return Math.max(minScale, Math.min(maxScale, newScale));
}
//....
function getXYFromTranslate(transform) {
// Create a dummy g for calculation purposes only. This will never
// be appended to the DOM and will be discarded once this function
// returns.
var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
// Set the transform attribute to the provided string value.
g.setAttributeNS(null, 'transform', transform);
// consolidate the SVGTransformList containing all transformations
// to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get
// its SVGMatrix.
var matrix = g.transform.baseVal.consolidate().matrix;
// As per definition values e and f are the ones for the translation.
return [matrix.e, matrix.f];
}
我找到了zooming with minimap这个很好的例子,但是它是用旧版本v3写的。我差点把它转换成 v4,但是 d3.event
有问题。在 v3 d3.event
中,似乎在调用缩放的两个元素之间共享缩放参数。因此,如果我在主 canvas 上缩放,然后在小地图 canvas 上缩放 - d3.event 将具有主 canvas 缩放 d3.event
和它将继续按原样缩放。但是在 v4 中,两个 d3 缩放事件都以某种方式具有单独的缩放或平移值。如文档中所示:
The zoom behavior stores the zoom state on the element to which the zoom behavior was applied, not on the zoom behavior itself. This is because the zoom behavior can be applied to many elements simultaneously, and each element can be zoomed independently.
但这留下了一个问题,我如何才能在两个元素上共享一个缩放事件?
编辑:
也非常感谢 Bill White,他在同一个主题中发布了他更新的文章 - D3 MINIMAP V4 UPDATE。这篇文章实际上是我在问题中发布的示例的更新。
可以找到问题的答案 here。
@mbostock 在问题线程中亲切地回答了我的问题。我还找到了另一个解决方案。我成功地将他的代码中的一些逻辑和公式直接实现到我的缩放处理程序中。它就像一个魅力。
所以这是从 v3 到 v4 的更新缩放处理程序:
注意:不包括翻译边界检查。
// ....
var scale = 1;
var minScale = .5;
var maxScale = 7.5;
var translation = [0, 0];
// ....
// Used for both main canvas and minimap zoom
var zoom = d3.zoom()
.on('zoom', zoomHandler);
function zoomHandler(newScale) {
var prevScale = scale;
var previousTranslation = getXYFromTranslate(panCanvas.attr('transform'));
var isZoomEvent = d3.event && d3.event.sourceEvent.deltaY;
var isDragEvent = d3.event && (d3.event.sourceEvent.movementX || d3.event.sourceEvent.movementY);
if (isZoomEvent) {
scale = calculateNewScale(prevScale, d3.event.sourceEvent);
scale = checkScaleBounderies(scale);
var mousePosition = d3.mouse(this);
// Based on d3.js zoom algorythm
translation[0] = mousePosition[0] - ((mousePosition[0] - previousTranslation[0]) / prevScale) * scale;
translation[1] = mousePosition[1] - ((mousePosition[1] - previousTranslation[1]) / prevScale) * scale;
} else if (isDragEvent) {
translation[0] = previousTranslation[0] + d3.event.sourceEvent.movementX;
translation[1] = previousTranslation[1] + d3.event.sourceEvent.movementY;
} else if (newScale) {
scale = newScale;
}
// Apply the new dimensions to the main canvas
panCanvas.attr('transform', 'translate(' + translation + ') scale(' + scale + ')');
// Apply the new dimensions to the minimap
minimap.scale(scale).render();
}
// Calculate the new scale value based on d3.js zoom formula
function calculateNewScale(prevScale, event) {
return prevScale * Math.pow(2, -event.deltaY * (event.deltaMode ? 120 : 1) / 500);
}
// Check if scale has reached max or min
function checkScaleBounderies(newScale) {
return Math.max(minScale, Math.min(maxScale, newScale));
}
//....
function getXYFromTranslate(transform) {
// Create a dummy g for calculation purposes only. This will never
// be appended to the DOM and will be discarded once this function
// returns.
var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
// Set the transform attribute to the provided string value.
g.setAttributeNS(null, 'transform', transform);
// consolidate the SVGTransformList containing all transformations
// to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get
// its SVGMatrix.
var matrix = g.transform.baseVal.consolidate().matrix;
// As per definition values e and f are the ones for the translation.
return [matrix.e, matrix.f];
}