I have a complex set of shapes that are in the snippet. They are rendered with React but I'm really just looking for some pointers about how I would go about being able to zoom these shapes in and out.
My googling is failing and I can only really find examples of graphs.
How can zoom in and out complex structures like this?
<svg 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>
5 Answers
Answers 1
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>
Answers 2
Scaling in svg is done with viewBox, which combines both scaling and offset. There is also nice article. 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.getAttribute("width"),h:svgImage.getAttribute("height")}; svgImage.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`); const svgSize = {w:svgImage.getAttribute("width"),h:svgImage.getAttribute("height")}; 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.x;//mouse x var my = e.y; 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:
Answers 3
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.
Answers 4
When I failed to find leightweight script for zooming (none of the tested were able to keep mouse cursor as centerpoint), I made "a bit" research and ended up with a solution my own. I found the easiest way to use viewbox.
Outer svg must have viewBox declared (or it should be created at the beginning of function).
<svg id="svgImage" width="900" height="500" viewBox="0 0 900 500"> <style>text { fill: white; }</style> <g id="par" 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> <script> const svgImage = document.getElementById("svgImage"); const svgSize = {w:svgImage.clientWidth ,h:svgImage.clientHeight}; var oldScale = 1; svgImage.onmousewheel = function(e) { e.preventDefault(); var svgW = svgSize.w, svgH = svgSize.h, mX = e.offsetX, mY = e.offsetY, delta = (e.wheelDelta) ? -e.wheelDelta : e.detail, newScale = oldScale + (oldScale*delta/1200); //1200: intensity var vb = svgImage.getAttribute('viewBox').split(" "); var newW = svgW * newScale, newH = svgH * newScale, newX = vb[0]*1 + (vb[2]*1 - newW) * (mX/svgW), newY = vb[1]*1 + (vb[3]*1 - newH) * (mY/svgH); viewBox = { x:Math.round(newX), y:Math.round(newY), w:newW, h:newH }; svgImage.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`); oldScale = newScale; </script>
PS. Very good series of articles about SVG coordinate system manipulation (and not only) is written by Sara Soueidan. Good blog to dig around.
Answers 5
There are several ways to deal with SVG
s in React
and React Native
, but I definitely use the simplest way, hence, using the font technology.
following steps show the best clear way to use SVG
s and deal with them:
- Collecting all
SVG
s and put all of them in a folder. (better: rename and enumerate them like 1_lablab 2_laflaf 3_etc) - Register in icomoon and press import icons to upload all
SVG
in the created folder - press generate font to generate your font that just contains your
SVG
s - Use Online font converter to make other extensions of font for cross-browser
The online font converter gives you several extensions toward its CSS
. after embedding your font to your project, assign each SVG
shape to an element className
, then with font-size
you can scale it very easy.
For example, I put a simple sample code below:
@font-face { font-family: 'icons'; src: url('../font/icon/icons.eot?xwgbwn'); src: url('../font/icon/icons.eot?xwgbwn#iefix') format('embedded-opentype'), url('../font/icon/icons.ttf?xwgbwn') format('truetype'), url('../font/icon/icons.woff?xwgbwn') format('woff'), url('../font/icon/icons.svg?xwgbwn#icons') format('svg'); font-weight: normal; font-style: normal } [class^="icon-"], [class*=" icon-"] { font-family: 'icons' !important; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale } .icon-tech-m:before { content: "\e949"; }
Ok, now when I assign icon-tech-m
to an element className I can see my
SVGas a font-shape an I can scale it easily just with
font-size`.
... renderThatSVG = () => ( <span className="icon-tech-m" /> ); ...
For more sample, you can see DiGiKala Mag, I wrote all of them alone, it is the biggest middle-east e-commerce. in this version, I just use font-shape to deal with SVG
s.
0 comments:
Post a Comment