how to zoom in on a complex svg structure
You can simply use css transform
to scale the svg. Set the csstransform-origin
from where you want to "pin" the zoom origin and use scale(x)
in transform
like in the above example that uses a range input element with minimum value to 1
and maximum to 200
for scalling from 1%
to 200%
:
const slider = document.getElementById("zoomRange");
const zvgZoom = document.getElementById("svgZoom");
const zoomValue = document.getElementById("zoomValue");
slider.oninput = function() {
//console.log('zoom', this.value / 100);
zoomValue.innerText = `${this.value}%`;
zvgZoom.style.transform = `scale(${this.value / 100})`;
}
#svgContainer {
background-color: #dedede;
}
#svgZoom {
transform-origin: 0% 0%;
}
<input type="range" min="1" max="200" value="100" class="slider" id="zoomRange">
<span id="zoomValue">100%</span>
<div id="svgContainer">
<svg id="svgZoom" height="767" width="903">
<g class="vx-group vx-tree" transform="translate(20, 70)">
<g class="vx-group" transform="translate(0, 70)">
<g class="vx-group" transform="translate(0, 0)">
<path class="vx-link-vertical" d="M451.5,0C451.5,233.5,451.5,233.5,451.5,467" percent="0.5" stroke="#f7f7f3" stroke-width="1" stroke-opacity="0.2" fill="none"></path>
</g>
<g class="vx-group" transform="translate(0, 0)">
<g class="vx-group" transform="translate(451.5, 0)" opacity="1">
<g class="vx-group node__container" transform="translate(0, 0)">
<svg class="" x="0" y="0" style="overflow: visible;">
<polygon points="25.98076211353316,-14.999999999999998 25.98076211353316,14.999999999999998 1.83697019872103e-15,30 -25.98076211353316,14.999999999999998 -25.980762113533157,-15.000000000000004 -5.510910596163089e-15,-30" class="node__hexagon"></polygon>
</svg>
<g class="vx-group node__business-unit" transform="translate(0, 0)">
<use xlink:href="#icon-BusinessUnit"></use>
</g>
<g class="hierarchy-label__container" transform="translate(0, -40)">
<path class="" d="
M 0.0078125, 5.15625
L 34.64882865137755,25.156249999999996
M -0.9921875, 5.15625
L -34.63320365137754,25.156249999999996
H -65.8515625
a8,8 0 0 1 -8,-8
V -47.15625
a8,8 0 0 1 8,-8 H 65.8515625 a8,8 0 0 1 8,8
L 73.8515625, 17.156249999999996
a8,8 0 0 1 -8,8
L 34.64882865137755, 25.156249999999996
Z
"></path>
<svg x="0" y="0" style="overflow: visible;">
<text class="hierarchy-label__item__name" width="150" y="-25" x="0" text-anchor="middle" style="pointer-events: none;">
<tspan x="0" dy="0em">Finance</tspan>
</text>
</svg>
<svg x="0" y="0" style="overflow: visible;">
<text class="hierarchy-label__item__type" width="150" y="-5" x="0" text-anchor="middle" style="pointer-events: none;">
<tspan x="0" dy="0.71em">Business Unit</tspan>
</text>
</svg>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
Zooming and panning are common and useful techniques in data visualization, which work particularly well with SVG based visualization since vector graphic does not suffer from pixelation as its bitmap counterpart would.
This answer explores D3's built-in support for both zooming and panning, by Nick Qi Zhu's book Data Visualization with D3.js Cookbook (2013).
Start by opening your local copy of the following file in your web browser:
https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter10/zoom.html.
In this recipe will implement geometric zooming and panning using D3 zoom support. Let's see how this is done in code:
<script type="text/javascript">
var width = 960, height = 500, r = 50;
var data = [
[width / 2 - r, height / 2 - r],
[width / 2 - r, height / 2 + r],
[width / 2 + r, height / 2 - r],
[width / 2 + r, height / 2 + r]
];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(
d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoom)
)
.append("g");
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", r)
.attr("transform", function (d) {
return "translate(" + d + ")";
});
function zoom() {
svg.attr("transform", "translate("
+ d3.event.translate
+ ")scale(" + d3.event.scale + ")");
}
</script>
This recipe generates the following zooming and panning effect:
Original:
Zoom:
Pan:
You will notice zooming and panning reacts perfectly well to both mouse wheel and multi-touch gesture (as opposing to the other answers). Most of the heavy lifting is done by D3 library so we needed little code.
Scaling in svg is done with viewBox, which combines both scaling and offset. There is a nice article How to Scale SVG. From the following article:
If you think of the document as a canvas, the view box is part of the canvas you want the viewer to see.
It's like a screen of your cell phone in the camera app which shows part of the scene which is observed with specified scale and offsets.
Nice sample which demonstrates what is viewBox can be found here.
A little math and I implemented zoom in/zoom out with mousewheel. In addition added panning with mousemove and display scale value. An example which demonstrates how viewBox can be used:
const svgImage = document.getElementById("svgImage");
const svgContainer = document.getElementById("svgContainer");
var viewBox = {x:0,y:0,w:svgImage.clientWidth,h:svgImage.clientHeight};
svgImage.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`);
const svgSize = {w:svgImage.clientWidth,h:svgImage.clientHeight};
var isPanning = false;
var startPoint = {x:0,y:0};
var endPoint = {x:0,y:0};;
var scale = 1;
svgContainer.onmousewheel = function(e) {
e.preventDefault();
var w = viewBox.w;
var h = viewBox.h;
var mx = e.offsetX;//mouse x
var my = e.offsetY;
var dw = w*Math.sign(e.deltaY)*0.05;
var dh = h*Math.sign(e.deltaY)*0.05;
var dx = dw*mx/svgSize.w;
var dy = dh*my/svgSize.h;
viewBox = {x:viewBox.x+dx,y:viewBox.y+dy,w:viewBox.w-dw,h:viewBox.h-dh};
scale = svgSize.w/viewBox.w;
zoomValue.innerText = `${Math.round(scale*100)/100}`;
svgImage.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`);
}
svgContainer.onmousedown = function(e){
isPanning = true;
startPoint = {x:e.x,y:e.y};
}
svgContainer.onmousemove = function(e){
if (isPanning){
endPoint = {x:e.x,y:e.y};
var dx = (startPoint.x - endPoint.x)/scale;
var dy = (startPoint.y - endPoint.y)/scale;
var movedViewBox = {x:viewBox.x+dx,y:viewBox.y+dy,w:viewBox.w,h:viewBox.h};
svgImage.setAttribute('viewBox', `${movedViewBox.x} ${movedViewBox.y} ${movedViewBox.w} ${movedViewBox.h}`);
}
}
svgContainer.onmouseup = function(e){
if (isPanning){
endPoint = {x:e.x,y:e.y};
var dx = (startPoint.x - endPoint.x)/scale;
var dy = (startPoint.y - endPoint.y)/scale;
viewBox = {x:viewBox.x+dx,y:viewBox.y+dy,w:viewBox.w,h:viewBox.h};
svgImage.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`);
isPanning = false;
}
}
svgContainer.onmouseleave = function(e){
isPanning = false;
}
<span id="zoomValue">1</span>
<div id="svgContainer">
<svg id="svgImage" height="964" width="767">
<g class="vx-group vx-tree" transform="translate(20, 70)">
<g class="vx-group" transform="translate(0, 70)">
<g class="vx-group" transform="translate(0, 0)">
<path class="vx-link-vertical" d="M451.5,0C451.5,233.5,451.5,233.5,451.5,467" percent="0.5" stroke="#f7f7f3" stroke-width="1" stroke-opacity="0.2" fill="none"></path>
</g>
<g class="vx-group" transform="translate(0, 0)">
<g class="vx-group" transform="translate(451.5, 0)" opacity="1">
<g class="vx-group node__container" transform="translate(0, 0)">
<svg class="" x="0" y="0" style="overflow: visible;">
<polygon points="25.98076211353316,-14.999999999999998 25.98076211353316,14.999999999999998 1.83697019872103e-15,30 -25.98076211353316,14.999999999999998 -25.980762113533157,-15.000000000000004 -5.510910596163089e-15,-30" class="node__hexagon"></polygon>
</svg>
<g class="vx-group node__business-unit" transform="translate(0, 0)">
<use xlink:href="#icon-BusinessUnit"></use>
</g>
<g class="hierarchy-label__container" transform="translate(0, -40)">
<path class="" d="
M 0.0078125, 5.15625
L 34.64882865137755,25.156249999999996
M -0.9921875, 5.15625
L -34.63320365137754,25.156249999999996
H -65.8515625
a8,8 0 0 1 -8,-8
V -47.15625
a8,8 0 0 1 8,-8 H 65.8515625 a8,8 0 0 1 8,8
L 73.8515625, 17.156249999999996
a8,8 0 0 1 -8,8
L 34.64882865137755, 25.156249999999996
Z
"></path>
<svg x="0" y="0" style="overflow: visible;">
<text class="hierarchy-label__item__name" width="150" y="-25" x="0" text-anchor="middle" style="pointer-events: none;">
<tspan x="0" dy="0em">Finance</tspan>
</text>
</svg>
<svg x="0" y="0" style="overflow: visible;">
<text class="hierarchy-label__item__type" width="150" y="-5" x="0" text-anchor="middle" style="pointer-events: none;">
<tspan x="0" dy="0.71em">Business Unit</tspan>
</text>
</svg>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
Math: