I found this nice example of zooming with minimap, but it is written in the old version v3. I almost convert it to v4 but there is a problem with d3.event
. In v3 d3.event
seems to share the zooming params between the two elements where the zoom is called. So if I scale on the main canvas and then on the minimap canvas - the d3.event will have the last scale value of the main canvas zoom d3.event
and it will continue with the zoom as it should be. But in v4 the both d3 zoom events have somehow separate values for scale or translate. As in the documentation:
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.
But that leaves the question how I can have one shared zoom event on two elements?
EDIT :
Great thanks also to Bill White, who posted in the same thread his updated article - D3 MINIMAP V4 UPDATE. The article is actually an update of the example I posted in my question.
Answers to the question can be found here.
@mbostock kindly answered my question in an issue thread. Also I found another solution. I succeeded with implementing some of the logic and formulas from his code directly into my zoom handler. It works like a charm.
So this is the updated zoom handler from v3 to v4:
Note: Translation boundery checks not included.
// ....
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];
}