I have a couple of 100 text elements in an SVG that come from an external SVG editor and I'd like to change their matrix transform using D3.js. They all have a horizontal scaling of 1.1 and i'd like to change it to 1.05 and put the matrix format into the form "translate(dx,dy)rotate(θ)skewX(φ)scale(kx,ky)". I've looked up on the D3 API to return the transform values but don't know how to change them and put it in the new transform form.
D3.JS API transform
Here's my code
var titles = d3.selectAll(".text-title");
for (i = 0; i < titles.length; i++) {
var titleTransform = d3.select(titles[i]).attr("transform");
var titleScale = d3.transform(titleTransform).scale;
//Change scale to 1.05 but keep the other transform values
}
Here's my svg
<svg>
<text transform="matrix(1.1 0 0 1 10 20)" class="text-title"> FOO </text>
<text transform="matrix(1.1 0 0 1 50 30)" class="text-title">
<tspan x="0" y="0">FOO</tspan>
<tspan x="-1.9" y="15">BAR</tspan>
</text>
<text transform="matrix(1.1 0 0 1 20 80)" class="text-title">
<tspan x="0" y="0">FOO</tspan>
<tspan x="-.2" y="15">BAR</tspan>
</text>
<text transform="matrix(1.1 0 0 1 100 30)" class="text-title"> GRUNGE BAR</text>
<text transform="matrix(1.1 0 0 1 100 50)" class="text-title">
<tspan x="0" y="0">FUNKY</tspan>
<tspan x="4.9" y="15">FOO</tspan>
</text>
</svg>
and a Fiddle
A selection isn't an array you can iterate over but there is an each method which is called on all elements. After that refctoring you can do something like this...
function foo(d, i) {
var titleTransform = d3.transform(d3.select(this).attr("transform"));
//Change scale to 1.05 but keep the other transform values
titleTransform.scale[0] = 1.05
d3.select(this).attr("transform", titleTransform);
}
var titles = d3.selectAll(".text-title").each(foo);
or as a fiddle
Related
I'm looking to create a text effect in which lines of text automatically scale to hit a specifically defined width, with auto-adjusting heights.
Ideally, I would then be able to stack multiple words on top of one another to achieve something visually similar to the below.
Example image
Is this something that should be possible with SVG text? Could it be done through pure CSS?
In the next example I'm using yourtext but you can change it. The main idea is using textLength to set thelength of the text.
The lengthAdjust attribute controls how the text is stretched into the length defined by the textLength attribute.
In this case I'm using lengthAdjust="spacingAndGlyphs" but you may want to use spacing instead.
Please observe that the top and bottom text have a dx attribute that indicates a shift along the y-axis on the position of the tspan element. In thise case I'm choosing 16 (as the sont size)
also please observe that the text is centered around the point x:0,y:0. You can choose a different one.
svg {
font-family:arial;
font-weight:bold;
font-size:16px;
width: 90vh;
border: solid;
}
<svg viewBox="-50 -50 100 100">
<text text-anchor="middle" dominant-baseline="middle">
<tspan dy="-16" x="0" textLength="70" lengthAdjust="spacingAndGlyphs" id="top">EXAMPLE</tspan>
<tspan y="0" x="0" textLength="70" lengthAdjust="spacingAndGlyphs" id="mid">TEXT</tspan>
<tspan dy="16" x="0" textLength="70" lengthAdjust="spacingAndGlyphs" id="bottom">GOES HERE</tspan>
</text>
</svg>
Quite likely, you should include some javaScript to get the desired result.
The main problem:
svg <text> elements don't have anything like multi line text or line heights. So you need to split your text content into a lot of <tspan> elements with different y offsets to emulate something similar to a HTML <p>.
Besides, a lot of important properties can't yet be stylesd with css. Most importantly x and y which are crucial to mimic a line height.
Example: mimic multi line svg text - scale font-size to width
let svg = document.querySelector('svg')
let svgPseudoP = document.querySelector('.svgPseudoP');
svgSplitTextLines(svgPseudoP)
//split newlines
function svgSplitTextLines(el) {
let texts = el.querySelectorAll('text');
for (let t = 0; t < texts.length; t++) {
let text0 = texts[t];
let [x0, y0] = [text0.getAttribute('x'), text0.getAttribute('y')];
//trim empty elements and whitespace
let lines = text0.innerHTML.split(/\r?\n/);
lines = lines.map((str) => {
return str.trim()
}).filter(Boolean)
//set first line as textContent
text0.textContent = lines[0];
// calculate proportions
let width0 = text0.getComputedTextLength();
let style0 = window.getComputedStyle(text0);
let fontSize0 = parseFloat(style0.getPropertyValue('font-size'));
// ratio between capital letter height and font size
let ascenderRatio = 0.71582;
// define ideal leading
let leading = fontSize0 * 0.2;
for (let i = 1; i < lines.length; i++) {
let str = lines[i];
let tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.textContent = str;
tspan.setAttribute('x', x0);
text0.appendChild(tspan);
// scale font size according to width
let width = tspan.getComputedTextLength();
let scale = width0 / width;
let newFontSize = parseFloat(fontSize0) * scale;
tspan.setAttribute('style', 'font-size:' + newFontSize + 'px');
// emulate line height by increasing Y offset
let tspanPrev = tspan.previousElementSibling;
let yPrev = tspanPrev ? +tspanPrev.getAttribute('y') : +text0.getAttribute('y');
let newY = yPrev + (newFontSize * ascenderRatio)
tspan.setAttribute('y', newY + leading);
}
}
}
svg {
width: 50%;
border: 1px solid #ccc;
}
text {
font-family: Arial;
font-weight: bold;
text-anchor: middle;
text-transform: uppercase;
}
<svg class="svgPseudoP" viewBox="0 0 100 100">
<text x="50%" y="20" font-size="10">
Example
Text
Goes here
</text>
<text x="25%" y="60" font-size="8">
Example2
Text
Goes
here
</text>
</svg>
You will essentially need these steps:
set a desired line width that all lines should get (this could e.g be the first line/text element)
get each line's width via text.getComputedTextLength()
scale the font-size accordingly:
let scale = widthIdeal / widthCurrentLine;
let newFontSize = fontSizeFirst * scale
calculate line height/leading
This will require to get the ratio between capital letter height and the
fonts em square – otherwise lines with larger font sizes will add larger margins than smaller ones.
E.g write a capital in Arial at 100 points in inkscape, Illustrator etc. and convert it to paths/outlines and check it's height: 71.582 pt
So the capital to em square ratio is: 100/71.582 = 0.71582
This value depends on the actual font files metrics – so there is nor standardized capital letter height. However a ratio about 0.72–0.75 should be fine for a lot of font families.
Example: Uneven leading due to not ideal capital to em square ratio.
The above example code will also split markup based new lines to <tspan> elements:
<text x="50%" y="20" font-size="10">
Example
Text
Goes here
</text>
will be converted to:
<text x="50%" y="20" font-size="10">
Example
<tspan x="50%" style="font-size:18.9px" y="35.5">Text</tspan>
<tspan x="50%" style="font-size:8.1px" y="43.3">Goes here</tspan>
</text>
Using the answer from this thread I was able to draw a semicircle (arc):
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
function describeArc(x, y, radius, startAngle, endAngle) {
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");
console.log(d)
return d;
}
window.onload = function() {
document.getElementById("arc1").setAttribute("d", describeArc(100, 100, 50, -90, 90));
};
<svg width="1000" height="1000">
<path id="arc1" fill="red" stroke="#446688" stroke-width="2" />
</svg>
What I'm trying to achieve is to be able to draw an SVG as a path consistent with many arcs (semicircles) and be able to set fill on them.
Something like this:
<svg xmlns="http://www.w3.org/2000/svg">
<path d="M 50 100 A 10 10 0 0 1 100 100 M 100 100 A 10 10 0 0 1 150 100 M 150 100 A 10 10 0 0 1 200 100 M 200 100 A 10 10 0 0 1 250 100" fill="red" stroke="blue" stroke-width="3" />
</svg>
Is there a better way to achieve a simpler path? For now, it looks like this:
<svg xmlns="http://www.w3.org/2000/svg">
<path d="M 50 100 A 10 10 0 0 1 100 100 M 100 100 A 10 10 0 0 1 150 100 M 150 100 A 10 10 0 0 1 200 100 M 200 100 A 10 10 0 0 1 250 100" fill="red" stroke="blue" stroke-width="3" />
</svg>
Or do I have to generate a longer and longer path when there are, let's say, 30 semicircles?
Edit: the IE9+ support is required. Also, those elements will be clickable, draggable and controllable. By controllable I mean that their number and size will change when mouse clicking/moving.
I choose my first approach with a dynamic very long path.
Thanks!
For this I would use lower case commands. For example this is drawing the arc you need: an arc with a radius of 25 and an ending point 50 units ( 2 * 25 ) away from the starting point of the arc.
<svg xmlns="http://www.w3.org/2000/svg">
<path d="M 50 100 a 25 25 0 0 1 50 0" fill="red" stroke="blue" stroke-width="3" />
</svg>
In order to get a path of 4 arcs you need to repeat the arc (a 25 25 0 0 1 50 0) 4 times something like this:
<svg xmlns="http://www.w3.org/2000/svg">
<path d="M 50 100 a 25 25 0 0 1 50 0
a 25 25 0 0 1 50 0
a 25 25 0 0 1 50 0
a 25 25 0 0 1 50 0 " fill="red" stroke="blue" stroke-width="3" />
</svg>
It's easy to see how you can use javascript to generate the d attribute you need:
let d ="M 50 100";
for(let i=0; i<4;i++){d +="a 25 25 0 0 1 50 0 "}
document.querySelector("path").setAttribute("d",d);
<svg xmlns="http://www.w3.org/2000/svg">
<path d="M 50 100" fill="red" stroke="blue" stroke-width="3" />
</svg>
You can use a vanilla JavaScript Web Component (supported in all modern Browsers) to create the SVG
Your Custom Element <svg-arcs repeat="7"></svg-arcs> then creates:
<style>
svg { background: pink }
svg path { stroke-width: 3 }
</style>
<svg-arcs repeat="30"></svg-arcs>
<script>
customElements.define("svg-arcs", class extends HTMLElement {
connectedCallback() {
let repeat = this.getAttribute("repeat") || 5;
let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
for (let x = 0; x < repeat; x++) {
let path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", `M${3 + 50*x} 100 A 10 10 0 0 1 ${50+50*x} 100`);
path.setAttribute("fill", "red");
path.setAttribute("stroke", "blue");
svg.append(path);
}
svg.setAttribute("viewBox", `0 0 ${50*repeat + 3} 150`);
this.append(svg);
}
})
</script>
For more dynamic control over individual arcs see the Web Component in SO post:
Firefox: shadow-DOM compatibility
You could use a pattern and size your patterned object appropriately. Here is one that accomodates 4 iterations.
Edit & Update:
If you want those arcs to be independently clickable/draggable, then they need to be separately addressable in the DOM. The "use" element might be what you're looking for.
svg {
background: grey;
}
<svg width="800px" height="600px">
<defs>
<path id="arc-template" d="M1.5 50 a 10 10 0 0 1 97 0" fill="red" stroke="blue" stroke-width="3" />
</defs>
<use id="arc1" href="#arc-template" x="50" y="100"/>
<use id="arc2" href="#arc-template" x="150" y="100"/>
<use id="arc3" href="#arc-template" x="250" y="100"/>
<use id="arc4" href="#arc-template" x="350" y="100"/>
</svg>
I need to check the response of a user by tracking the mouse movement over a moving object (in this case a circle). If the mouse is not over the circle I need to calculate the offset by comparing the mouse coordinates and the circle coordinates.
But whenever I check the circle values, they are not changing and will stay on their initial value.
Here's a simple example:
function clickCircle() {
var circle = document.getElementById("circle");
console.log('baseVal x: ' + circle.cx.baseVal.value);
console.log('animVal x: ' + circle.cx.animVal.value);
}
<p>Click on the moving circle</p>
<svg width="1200" height="1200">
<circle id="circle" cx="60" cy="60" r="20" fill="green" onclick="clickCircle();">
<animateMotion id="ani" dur="10s" repeatCount="indefinite"
path="M20, 60 C20,
-50 180, 150 180,
60 C180-60 20,
150 20, 60 z" />
</circle>
</svg>
Does anybody have any idea on how to get the coordinates from a moving circle that is being animated with animateMotion?
You could drag in an animation Icon, and track its properties
Or with JavaScript you calculate its center x,y position with:
let {width,height} = circle.getBBox();
let {x,y} = circle.getBoundingClientRect();
x = x + width/2;
y = y + height/2;
Also read: https://schneide.blog/2018/03/05/some-tricks-for-working-with-svg-in-javascript/
note! this code below will forever add circle Nodes!
<style> svg { width: 300px } </style>
<svg viewBox="0 0 300 200">
<rect width="100%" height="100%" fill="lightgreen"></rect>
<text x=10 y="20">Click the circle!</text>
<circle id="circle" cx="40" cy="40" r="40" fill="green"
onclick="clickCircle(event)">
<animateMotion id="ani" dur="10s" repeatCount="indefinite" path="m20 40c0-110 160 90 160 0c0-120-160 90-160 0z" />
</circle>
<text id="position" x="200" y="20">21</text>
</svg>
<script>
function clickCircle(evt) {
point("gold");
}
function point(color) {
let circle = document.getElementById("circle");
let {width,height} = circle.getBBox();
let {x,y} = circle.getBoundingClientRect();
let c = document.createElementNS("http://www.w3.org/2000/svg", "circle");
x = x + width/2;
y = y + height/2;
c.setAttribute("cx", x);
c.setAttribute("cy", y);
c.setAttribute("r", color == "black" ? 3 : 6);
c.setAttribute("fill", color);
circle.parentNode.append(c);
position.innerHTML = `${~~x} , ${~~y}`;
}
setInterval(() => point("black"), 250);
</script>
Or try the JSFiddle: https://jsfiddle.net/dannye/ph705b49/
Dan was Authorware toch heel wat makkelijker...
Alles goed?
I had written a program for 14 numbers of different SVG objects.And I want to write a single loop program for each and every SVG object to get its point of center . I preferred using loop statement in my code with Javascript.
I had tried using JavaScript but it's not being looped repeatedly.
JavaScript code goes here;
var i=1;
while(i<15){
gear = document.getElementById("gear"+i);
let aa = gear.getBBox()
let a = {};
a.x = aa.x + aa.width/2;
a.y = aa.y + aa.height/2;
gear.setAttribute("style",`transform-origin:${a.x}px ${a.y}px` );
i++;
}
I had given unique id for each SVG object as (gear1, gear2, gear3,.....,gear14).
The final result must be looping the code for each and every SVG objects.
I would use querySelectorAll() for that. You can select all gears which ids start with "gear". And I wouldn't use a or aa as a variable name. It's just confusing.
let gears = document.querySelectorAll("[id^='gear']"); // get all gears
// Now convert "gears" into an array and loop it
Array.from(gears).forEach(function(gear){
let bbox = gear.getBBox();
let styles = {};
styles.x = bbox.x + bbox.width / 2;
styles.y = bbox.y + bbox.height / 2;
gear.setAttribute("style", `transform-origin:${styles.x}px ${styles.y}px`);
});
var i=1;
let gear =[]
while(i<3){
gear[i] = document.getElementById("gear"+i);
// alert(gear[i])
let aa = gear[i].getBBox();
let a = {};
a.x = aa.x + aa.width/2;
a.y = aa.y + aa.height/2;
gear[i].setAttribute("style",`transform-origin:${a.x}px ${a.y}px` );
i++;
}
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<g id="gear1">
<text x="5" y="16" transform="scale(2, 2)">Hello World!</text>
<text x="8" y="32" transform="translate(0 20) scale(1.25 1)">Hello World Again!</text>
</g>
<g id="gear2">
<text x="5" y="16" transform="scale(5, 8)">Hello World2!</text>
<text x="8" y="32" transform="translate(0 20) scale(1.25 1)">Hello World Again!</text>
</g>
<g id="gear3">
<text x="5" y="16" transform="scale(3, 5)">Hello World3!</text>
<text x="8" y="32" transform="translate(0 20) scale(1.25 1)">Hello World Again!</text>
</g>
</svg>
Trying to carry out this svg pan and zoom for two rectangles, I can't seem to get it to work. Really new at this but I was able to come up with this but it just doesn't seem to work . I have added my javascript code as well if anyone can seem t find what is wrong, i Used the group id for the elements to try to pan and zoom based on my js but it doesn't still work
<!DOCTYPE html>
<head>
</head>
<body>
<svg width="24cm" height="16cm" viewBox="-4 -1 26 20"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="myfield" transform="scale(1,1) translate(0,0)" >
<g transform=" scale(1,-1) translate(0,-16)"><g id="togglefield">
<rect id= "rectangle1" class= "rectangles" x= 2 y= 0 width= 4 height= 6 onClick="fieldDetails(event)"></rect>
<rect id="rectangle2" class= "rectangles" x= 2 y= 6 width= 4
height= 5 onClick="fieldDetails(event)"></rect>
</g>
<g id="togglefind">
<circle class="circles" id="find1" cx= 4 cy= 1 r="0.2" onClick="findDetails(event)"></circle>
<text class="text" x= 4 y= 1 transform="translate(0 2 ) scale(-1,1) rotate(180)"> 1 </text>
<circle class="circles" id="find5" cx= 4 cy= 7 r="0.2" onClick="findDetails(event)"></circle>
<text class="text" x= 4 y= 7 transform="translate(0 14 ) scale(-1,1) rotate(180)"> 5 </text>
</g>
</g>
<rect x="12.6" y="12.4" width="3" height="3" fill="white" stroke="grey" stroke-width="0.05"/>
<text x="13" y="13.2" font-family="Arial" font-size="0.6">Legend</text>
<g id="Fieldlgd">
<rect x="12.8" y="13.6" width="0.6" height="0.6" fill="none" stroke="green" stroke-width="0.04"/>
<text x="13.6" y="14" font-family="Georgia" font-size="0.5">Field ID</text>
<text x="13" y="14" font-family="Georgia" font-size="0.5">1</text>
</g><g id="Findlgd">
<circle cx="13.1" cy="14.7" r="0.12" fill="blue" stroke="red" stroke-width="0.05"/>
<text x="13.6" y="14.85" font-family="Georgia" font-size="0.5">Find ID</text>
<text x="13.2" y="14.7" font-family="Arial" font-size="0.45">1</text>
</g>
</g>
<rect x="-4" y="0.3" width="3.2" height="12" fill="white" stroke="black" stroke-width="0" fill-opacity="0.8"/>
<text x="-4" y="0.8" font-family="San Serif" font-size="3%">Pan and Zoom </text>
<text x="-2.7" y="1.8" font-family="Georgia" font-size="4%">N</text>
<polyline id="panup" onclick="panup_click(evt)" points="-3,2 -2.5,1 -2,2 -2.3,1.9 -2.7,1.9 -3,2" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<text x="-2.7" y="3.4" font-family="Georgia" font-size="4%">S</text>
<polyline id="pandown" onclick="pandown_click(evt)" points="-3,2.8 -2.5,3.8 -2,2.8 -2.3,2.9 -2.7,2.9 -3,2.8" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<text x="-3.3" y="2.6" font-family="Georgia" font-size="4%">W</text>
<polyline id="panleft" onclick="panleft_click(evt)" points="-2.7,1.9 -3.7,2.4 -2.7,2.9 -2.8,2.6 -2.8,2.2 -2.7,1.9" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<text x="-2.1" y="2.6" font-family="Georgia" font-size="4%">E</text>
<polyline id="panright" onclick="panright_click(evt)" points="-2.3,1.9 -1.3,2.4 -2.3,2.9 -2.2,2.6 -2.2,2.2 -2.3,1.9" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<text x="-2.7" y="2.6" font-family="Georgia" font-size="4%">C</text>
<circle id="recenter" onclick="recenter_click(evt)" cx="-2.5" cy="2.4" r="0.3" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<polyline id="zoomcross" points="-2.6,4.2 -2.4,4.2 -2.4,4.6 -2.1,4.6 -2.1,4.8 -2.4,4.8 -2.4,5.3 -2.6,5.3 -2.6,4.8 -2.9,4.8 -2.9,4.6 -2.6,4.6 -2.6,4.2" fill="cornflowerblue"/>
<rect id="zoomIn" onclick="zoomin_click(evt)" x="-3" y="4" width="1" height="1.5" fill="white" stroke="black" stroke-width="0.05" opacity="0.2"/>
<rect id="zoomdash" x="-2.9" y="5.9" width="0.8" height="0.2"
fill="cornflowerblue"/>
<rect id="zoomOut" onclick="zoomout_click(evt)" x="-3" y="5.7" width="1" height="0.6" fill="white" stroke="black" stroke-width="0.05" opacity="0.2"/>
</svg>
// Pan left function
function panleft_click(evt) {
var root = document.documentElement;
// variable to get the group transform object
var myfield = null;
myfield = document.getElementById("myfield");
// Incase the group has no Id then get the first '<g>' element
if(myfield === null)
myfield = document.getElementsByTagName('g')[0];
var goIn = evt.target; // thus line is currently not used
// Fetch the current transform state of the group element into a
var matrix = myfield.getCTM();
// Read the translate attributes from the matrix into variable x and y
// Below is the desired leftward change
var x = (matrix.e + 2) / matrix.a ; // Divide change by current scale to remove exponential movement
var y = matrix.f / matrix.a ; // To keep motion only along one axis
// Set the new transform attribute for the group
myfield.setAttributeNS(null,"transform", "scale(" + matrix.a + "," + matrix.d + ") translate(" + x + "," + y + ")");
}
// Pan right function
function panright_click(evt) {
// variable to get the group transform object
var myfield = null;
myfield = document.getElementById("myfield");
// Incase the group has no Id then get the first '<g>' element
if(myfield === null)
myfield = document.getElementsByTagName('g')[0];
var goIn = evt.target; // thus line is currently not used
// Fetch the current transform state of the group element into a matrix
var matrix = myfield.getCTM();
// Read the translate attributes from the matrix into variable x and y
// Below is the desired rightward change
var x = (matrix.e - 2) / matrix.a; // Divide change by current scale to remove exponential movement
var y = matrix.f / matrix.a; // To keep motion only along one axis
// Set the new transform attribute for the group
myfield.setAttributeNS(null,"transform", "scale(" + matrix.a + "," + matrix.d + ") translate(" + x + "," + y + ")");
}
// Pan up function
function panup_click(evt) {
// variable to get the group transform object
var myfield = null;
myfield = document.getElementById("myfield");
// Incase the group has no Id then get the first '<g>' element
if(myfield === null)
myfield = document.getElementsByTagName('g')[0];
var goIn = evt.target; // thus line is currently not used
// Fetch the current transform state of the group element into a matrix
var matrix = myfield.getCTM();
// Read the translate attributes from the matrix into variable x and y
var x = matrix.e / matrix.a ; // Divided by scale to keep motion only along one axis
// Below is the desired upward change
var y = (matrix.f + 2) / matrix.a; // Divide change by current scale to remove exponential movement
// Set the new transform attribute for the group
myfield.setAttributeNS(null,"transform", "scale(" + matrix.a + "," + matrix.d + ") translate(" + x + "," + y + ")");
}
// Pan down function
function pandown_click(evt) {
// variable to get the group transform object
var myfield = null;
myfield = document.getElementById("myfield");
// Incase the group has no Id then get the first '<g>' element
if(myfield === null)
myfield = document.getElementsByTagName('g')[0];
var goIn = evt.target; // thus line is currently not used
// Fetch the current transform state of the group element into a matrix
var matrix = myfield.getCTM();
// Read the translate attributes from the matrix into variable x and y
var x = matrix.e / matrix.a; // Divided by scale to keep motion only along one axis
// Below is the desired downward change
var y = (matrix.f - 2) / matrix.a; // Divide change by current scale to remove exponential movement
// Set the new transform attribute for the group
myfield.setAttributeNS(null,"transform", "scale(" + matrix.a + "," + matrix.d + ") translate(" + x + "," + y + ")");
}
// Zoom in (enlarge) function
function zoomin_click(evt) {
// variable to get the group transform object
var myfield = null;
myfield = document.getElementById("myfield");
// Incase the group has no Id then get the first '<g>' element
if(myfield === null)
myfield = document.getElementsByTagName('g')[0];
var goIn = evt.target; // thus line is currently not used
// Fetch the current transform state of the group element into a matrix
var matrix = myfield.getCTM();
// Read the scale attributes from the matrix into variable x and y
// then apply an increment by multiplying
var x = matrix.a * 1.2;
var y = matrix.d * 1.2;
// Set the new transform attribute for the group
myfield.setAttributeNS(null,"transform", "scale(" + x + "," + y + ") translate(" + matrix.e + "," + matrix.f + ")");
}
// Zoom out (reduce) function
function zoomout_click(evt) {
// variable to get the group transform object
var myfield = null;
myfield = document.getElementById("myfield");
// Incase the group has no Id then get the first '<g>' element
if(myfield === null)
myfield = document.getElementsByTagName('g')[0];
var goIn = evt.target; // thus line is currently not used
// Fetch the current transform state of the group element into a matrix
var matrix = myfield.getCTM();
// Read the scale attributes from the matrix into variable x and y
// then apply a reduction in scale by dividing
var x = matrix.a / 1.2;
var y = matrix.d / 1.2;
// Set the new transform attribute for the group
myfield.setAttributeNS(null,"transform", "scale(" + x + "," + y + ") translate(" + matrix.e + "," + matrix.f + ")");
}
// Recenter/reset function
function recenter_click(evt) {
// Reset the group to the original transform
document.getElementById("myfield").setAttributeNS(null,"transform", "scale(1,1) translate(0,0)");
}
getCTM is the wrong function to use for this purpose. It includes any transforms applied to the parent SVG due to the viewBox etc. You are just wanting to manipulate the transform attribute of the "myfield" group.
I would recommend just manipulating the tranform attribute using the predefined DOM functions.
You access the transform attribute DOM by using:
<myfield DOM object>.transform.baseVal
That returns a SVGTransformList object, which is basically an array containing two SVGTramnsform objects. One for the scale() part, and one for the transform() part. Once you have those objects you can just give them new values, and the SVG will update.
function panup_click(evt) {
pan(0,-2);
}
function pandown_click(evt) {
pan(0,2);
}
function panleft_click(evt) {
pan(-2,0);
}
function panright_click(evt) {
pan(2,0);
}
// Pan function
function pan(dx, dy) {
// variable to get the group transform object
var myfield = document.getElementById("myfield");
// Fetch the current value of the transform attribute
var transformList = myfield.transform.baseVal;
var translate = transformList.getItem(1);
// Adjust the translate transform item
var tx = translate.matrix.e;
var ty = translate.matrix.f;
translate.setTranslate(tx + dx, ty + dy);
}
// Zoom in (enlarge) function
function zoomin_click(evt) {
zoom(1.2);
}
// Zoom out (reduce) function
function zoomout_click(evt) {
zoom(1 / 1.2);
}
function zoom(scaleFactor)
{
// variable to get the group transform object
var myfield = document.getElementById("myfield");
// Fetch the current value of the transform attribute
var transformList = myfield.transform.baseVal;
var scale = transformList.getItem(0);
// Adjust the translate transform item
var s = scale.matrix.a;
scale.setScale(s * scaleFactor, s * scaleFactor);
}
// Recenter/reset function
function recenter_click(evt) {
// Reset the group to the original transform
document.getElementById("myfield").setAttributeNS(null,"transform", "scale(1,1) translate(0,0)");
}
<svg width="24cm" height="16cm" viewBox="-4 -1 26 20"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="myfield" transform="scale(1,1) translate(0,0)" >
<g transform=" scale(1,-1) translate(0,-16)">
<g id="togglefield">
<rect id= "rectangle1" class= "rectangles" x= 2 y= 0 width= 4 height= 6 onClick="fieldDetails(event)"></rect>
<rect id="rectangle2" class= "rectangles" x= 2 y= 6 width= 4 height= 5 onClick="fieldDetails(event)"></rect>
</g>
<g id="togglefind">
<circle class="circles" id="find1" cx= 4 cy= 1 r="0.2" onClick="findDetails(event)"></circle>
<text class="text" x= 4 y= 1 transform="translate(0 2 ) scale(-1,1) rotate(180)"> 1 </text>
<circle class="circles" id="find5" cx= 4 cy= 7 r="0.2" onClick="findDetails(event)"></circle>
<text class="text" x= 4 y= 7 transform="translate(0 14 ) scale(-1,1) rotate(180)"> 5 </text>
</g>
</g>
<rect x="12.6" y="12.4" width="3" height="3" fill="white" stroke="grey" stroke-width="0.05"/>
<text x="13" y="13.2" font-family="Arial" font-size="0.6">Legend</text>
<g id="Fieldlgd">
<rect x="12.8" y="13.6" width="0.6" height="0.6" fill="none" stroke="green" stroke-width="0.04"/>
<text x="13.6" y="14" font-family="Georgia" font-size="0.5">Field ID</text>
<text x="13" y="14" font-family="Georgia" font-size="0.5">1</text>
</g>
<g id="Findlgd">
<circle cx="13.1" cy="14.7" r="0.12" fill="blue" stroke="red" stroke-width="0.05"/>
<text x="13.6" y="14.85" font-family="Georgia" font-size="0.5">Find ID</text>
<text x="13.2" y="14.7" font-family="Arial" font-size="0.45">1</text>
</g>
</g>
<rect x="-4" y="0.3" width="3.2" height="12" fill="white" stroke="black" stroke-width="0" fill-opacity="0.8"/>
<text x="-4" y="0.8" font-family="San Serif" font-size="3%">Pan and Zoom </text>
<text x="-2.7" y="1.8" font-family="Georgia" font-size="4%">N</text>
<polyline id="panup" onclick="panup_click(evt)" points="-3,2 -2.5,1 -2,2 -2.3,1.9 -2.7,1.9 -3,2" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<text x="-2.7" y="3.4" font-family="Georgia" font-size="4%">S</text>
<polyline id="pandown" onclick="pandown_click(evt)" points="-3,2.8 -2.5,3.8 -2,2.8 -2.3,2.9 -2.7,2.9 -3,2.8" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<text x="-3.3" y="2.6" font-family="Georgia" font-size="4%">W</text>
<polyline id="panleft" onclick="panleft_click(evt)" points="-2.7,1.9 -3.7,2.4 -2.7,2.9 -2.8,2.6 -2.8,2.2 -2.7,1.9" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<text x="-2.1" y="2.6" font-family="Georgia" font-size="4%">E</text>
<polyline id="panright" onclick="panright_click(evt)" points="-2.3,1.9 -1.3,2.4 -2.3,2.9 -2.2,2.6 -2.2,2.2 -2.3,1.9" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<text x="-2.7" y="2.6" font-family="Georgia" font-size="4%">C</text>
<circle id="recenter" onclick="recenter_click(evt)" cx="-2.5" cy="2.4" r="0.3" fill="lavender" stroke="cornflowerblue" stroke-width="0.05" fill-opacity="0.6"/>
<polyline id="zoomcross" points="-2.6,4.2 -2.4,4.2 -2.4,4.6 -2.1,4.6 -2.1,4.8 -2.4,4.8 -2.4,5.3 -2.6,5.3 -2.6,4.8 -2.9,4.8 -2.9,4.6 -2.6,4.6 -2.6,4.2" fill="cornflowerblue"/>
<rect id="zoomIn" onclick="zoomin_click(evt)" x="-3" y="4" width="1" height="1.5" fill="white" stroke="black" stroke-width="0.05" opacity="0.2"/>
<rect id="zoomdash" x="-2.9" y="5.9" width="0.8" height="0.2" fill="cornflowerblue"/>
<rect id="zoomOut" onclick="zoomout_click(evt)" x="-3" y="5.7" width="1" height="0.6" fill="white" stroke="black" stroke-width="0.05" opacity="0.2"/>
</svg>