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>
Related
My goal is to design an arc slider which looks something like that
I have the following structure of the template
<svg width="500" height="300">
<path id="track" stroke="lightgrey" fill="transparent" stroke-width="20" d="
M 50 50
A 90 90 0 0 0 300 50
"/>
<path id="trackFill" fill="cyan" stroke-width="20" d="
M 50 50
A 90 90 0 0 0 [some dynamic value?] [some dynamic value?]
"/>
<circle id="knob" fill="lightblue" cx="[dynamic, initial - 50]" cy="[dynamic, initial - 50]" r="25"/>
</svg>
knob - the control which user is supposed to drag in order to change the value
track - the full arc of the slide
trackFill - the portion of the slider path before the knob
Is it possible to make trackFill cover the portion of the slider before the knob as it is being dragged along the slider curve? If so which APIs or CSS rules will help me to achieve such a result?
Is it something like this you are after?
let svg = document.getElementById("slider");
let trackFill = document.getElementById("trackFill");
let knob = document.getElementById("knob");
let isDragging = false;
let sliderDragOffset = {dx: 0, dy: 0};
let ARC_CENTRE = {x: 175, y: 50};
let ARC_RADIUS = 125;
let sliderValue = 0;
setSliderValue(sliderValue);
function setSliderValue(value)
{
// Limit value to (0..sliderMax)
let sliderMax = track.getTotalLength();
sliderValue = Math.max(0, Math.min(value, sliderMax));
// Calculate new position of knob
let knobRotation = sliderValue * Math.PI / sliderMax;
let knobX = ARC_CENTRE.x - Math.cos(knobRotation) * ARC_RADIUS;
let knobY = ARC_CENTRE.y + Math.sin(knobRotation) * ARC_RADIUS;
// Adjust trackFill dash patter to only draw the portion up to the knob position
trackFill.setAttribute("stroke-dasharray", sliderValue + " " + sliderMax);
// Update the knob position
knob.setAttribute("cx", knobX);
knob.setAttribute("cy", knobY);
}
knob.addEventListener("mousedown", evt => {
isDragging = true;
// Remember where we clicked on knob in order to allow accurate dragging
sliderDragOffset.dx = evt.offsetX - knob.cx.baseVal.value;
sliderDragOffset.dy = evt.offsetY - knob.cy.baseVal.value;
// Attach move event to svg, so that it works if you move outside knob circle
svg.addEventListener("mousemove", knobMove);
// Attach move event to window, so that it works if you move outside svg
window.addEventListener("mouseup", knobRelease);
});
function knobMove(evt)
{
// Calculate adjusted drag position
let x = evt.offsetX + sliderDragOffset.dx;
let y = evt.offsetY + sliderDragOffset.dy;
// Position relative to centre of slider arc
x -= ARC_CENTRE.x;
y -= ARC_CENTRE.y;
// Get angle of drag position relative to slider centre
let angle = Math.atan2(y, -x);
// Positions above arc centre will be negative, so handle them gracefully
// by clamping angle to the nearest end of the arc
angle = (angle < -Math.PI / 2) ? Math.PI : (angle < 0) ? 0 : angle;
// Calculate new slider value from this angle (sliderMaxLength * angle / 180deg)
setSliderValue(angle * track.getTotalLength() / Math.PI);
}
function knobRelease(evt)
{
// Cancel event handlers
svg.removeEventListener("mousemove", knobMove);
window.removeEventListener("mouseup", knobRelease);
isDragging = false;
}
<svg id="slider" width="500" height="300">
<g stroke="lightgrey">
<path id="track" fill="transparent" stroke-width="20" d="
M 50 50
A 125 125 0 0 0 300 50
"/>
</g>
<use id="trackFill" xlink:href="#track" stroke="cyan"/>
<circle id="knob" fill="lightblue" cx="50" cy="50" r="25"/>
</svg>
I've kept this code simple for clarity, but at the expense of some limitations.
It assumes there is only one slider per page. If you need more than that, you will have to keep the slider-specific values (eg sliderValue and, isDragging) separate. You could use data attributes for that. You would also need to switch from accessing the SVG elements via id attributes to another way (eg. class attributes), because id attributes must be unique on the page.
Here is a simple example:
const radius = 50;
const offsetX = 10;
const offsetY = 10;
// 0 <= pos <= 1
const setSliderPos = (svg, pos) => {
const angle = Math.PI * pos;
const x = offsetX + radius - Math.cos(angle) * radius;
const y = offsetY + Math.sin(angle) * radius;
svg.select('.knob').attr('cx', x).attr('cy', y);
svg.select('.first').attr('d', `M ${offsetX},${offsetY} A ${radius},${radius} 0 0 0 ${x},${y}`);
svg.select('.second').attr('d', `M ${x},${y} A ${radius},${radius} 0 0 0 ${offsetX + radius * 2},${offsetY}`);
}
setSliderPos(d3.select('#svg-1'), 0.3);
setSliderPos(d3.select('#svg-2'), 0.6);
setSliderPos(d3.select('#svg-3'), 1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="svg-1" width="150" height="80">
<path class="first" stroke-width="5" stroke="lightblue" fill="none"/>
<path class="second" stroke-width="5" stroke="cyan" fill="none"/>
<circle class="knob" r="10" fill="lightblue"/>
</svg>
<svg id="svg-2" width="150" height="80">
<path class="first" stroke-width="5" stroke="lightblue" fill="none"/>
<path class="second" stroke-width="5" stroke="cyan" fill="none"/>
<circle class="knob" r="10" fill="lightblue"/>
</svg>
<svg id="svg-3" width="150" height="80">
<path class="first" stroke-width="5" stroke="lightblue" fill="none"/>
<path class="second" stroke-width="5" stroke="cyan" fill="none"/>
<circle class="knob" r="10" fill="lightblue"/>
</svg>
To mark the progress you can use stroke-dasharray with a percentage; for example
<g stroke="lightgrey">
<path id="track" fill="transparent" stroke-width="20"
stroke-dasharray="40% 60%"
d="M 50 50 A 125 125 0 0 0 300 50"/>
</g>
This will show 40% of the arc and hide 60% of the arc.
If you need to use two colors, for example the whole arc in grey and the progress in black, you need to use two arcs on top of one another; the one at the bottom would be the one you already have, and the one at the top would have a stroke in black and use stroke-dasharray as shown.
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?
In the following example I have in gold the intersection of 3 shapes (in this case I'm using circles but those 3 shapes can be anything) The golden intersection is the result of clipping with clip-path.
I would like to use the intersection as a symbol and for this I would need to know the bounding box of the intersection, i.e the red stroked rectangle.
If I'm using intersection.getBBox() I'm getting the bounding box before clipping.
How can I get the bounding box of the intersection?
console.log(intersection.getBBox())
svg{border:solid}
.circles{fill:none;stroke:black}
<svg id="svg" viewBox="-150 -150 300 300" width="300">
<defs>
<circle id="c1" cx="0" cy="-50" r="80"></circle>
<circle id="c2" cx="43.3" cy="25" r="80"></circle>
<circle id="c3" cx="-43.3" cy="25" r="80"></circle>
<clipPath id="clipC2"><use xlink:href="#c2"/></clipPath>
<clipPath id="clipC3"><use xlink:href="#c3"/></clipPath>
</defs>
<g class="circles">
<use xlink:href="#c1"/>
<use xlink:href="#c2"/>
<use xlink:href="#c3"/>
</g>
<g id="intersection">
<g clip-path="url(#clipC3)">
<use fill="gold" xlink:href="#c1" clip-path="url(#clipC2)"/>
</g>
</g>
<rect x="-38" y="-42" width="75" height="74" stroke="red" fill="none"/>
</svg>
The main idea is this:
I'm taking the svg element, make it base64 and use it as the src attribute of an image.
I'm painting the svg element on a canvas with the same size as the svg element.
I get the image data from the canvas
loop through the image data and get:
the smallest x value of a black pixel
the smallest y value of a black pixel
the biggest x value of a black pixel
the biggest y value of a black pixel
I'm using using those values to build the new viewBox value for the intersection.
//the svg's viewBox
let vB = { x: -100, y: -100, w: 200, h: 200 };
//canvas
let ctx = c.getContext("2d");
//set the size of the canvas equal to the size of the svg element
c.width = vB.w;
c.height = vB.h;
// draw the svg element on the canvas
let xml = new XMLSerializer().serializeToString(svg);
// make it base64 and use it as the src attribute of the image
let img=new Image()
img.src = "data:image/svg+xml;base64," + btoa(xml);
img.onload = function() {
//paint the image on the canvas
ctx.drawImage(this, 0, 0);
//get the image data from the canvas
let imgData = ctx.getImageData(0, 0, vB.w, vB.h).data;
// x the smallest x value of a black pixel
// y the smallest y value of a black pixel
// X the biggest x value of a black pixel
// Y the biggest y value of a black pixel
let x = vB.w,
y = vB.h,
X = 0,
Y = 0;
let n = 0;
for (let i = 0; i < imgData.length; i += 4) {
n++
if (imgData[i + 3] != 0) {
//if the alpha (i+3) value of the pixel is not 0
let _y = Math.ceil(i / (4 * vB.w));
let _x = (i / 4) % vB.w;
if (_x < x) { x = _x; }
if (_y < y) { y = _y; }
if (_x > X) { X = _x; }
if (_y > Y) { Y = _y; }
}
if(n==imgData.length/4){
let newViewBox = `${x + vB.x} ${y + vB.y} ${X - x + 1} ${Y - y}`;
reuleaux.setAttribute("viewBox", newViewBox);
console.log(`viewBox="${newViewBox}"`);
}
}
}
svg,
canvas {
outline: 1px solid;
}
<svg id="svg" viewBox="-100 -100 200 200" width="200">
<defs>
<circle id="c1" cx="0" cy="-50" r="80"></circle>
<circle id="c2" cx="43.3" cy="25" r="80"></circle>
<circle id="c3" cx="-43.3" cy="25" r="80"></circle>
<clipPath id="clipC2"><use xlink:href="#c2"/></clipPath>
<clipPath id="clipC3"><use xlink:href="#c3"/></clipPath>
</defs>
<g id="intersection">
<g clip-path="url(#clipC3)">
<use xlink:href="#c1" clip-path="url(#clipC2)"/>
</g>
</g>
</svg>
<!--<img id="img" width="200" height="200"/>-->
<canvas id="c"></canvas>
<svg id="reuleaux" viewBox="-100 -100 200 200" width="200" style="background:#dfdfdf">
<use xlink:href="#intersection"/>
</svg>
Unsure if this was the type of thing you were after.
let myBB = {
x: c2.getBBox().x,
get y() {
return c1.getBBox().y + this.width
},
get width() {
// return (posNum(c3.getBBox().x)) - (posNum(myBB.x));
let leftPointOfWidth = c2.getBBox().x;
let rightPointofWidth = c3.getBBox().x + c3.getBBox().width;
// 10000 to guarantee both positive numbers. very hacky
let mywidth = (rightPointofWidth + 10000) - (leftPointOfWidth + 10000);
return mywidth;
},
get height() {
return this.width;
}
}
I'm quite sure there's a better way to put it. And need to call the getters; They won't appear in the console.log(myBB)
Inputting the obtained coordinates and width gives the rect in yellow. (Pink rects are shoing centres of circles)
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>
I'm a bit lost computing matrix transform (rotate / translate)
I'm applying a svg transform to a svg rect (r1)
<svg width="400" height="400" viewBox="0 0 400 400"
xmlns="http://www.w3.org/2000/svg">
<g id="r1" transform="translate(20,100) rotate(30, 175, 85)">
<rect width="350" height="170" fill="#69c" fill-opacity=".1" />
</g>
<g id="r2" transform="">
<rect width="150" height="100" fill="green" fill-opacity=".1" />
</g>
<g id="r3" transform="">
<rect width="150" height="100" fill="red" fill-opacity=".1" />
</g>
</svg>
this give me the following transform matrix
SVGMatrix { a: 0.8660253882408142, b: 0.5, c: -0.5, d: 0.8660253882408142, e: 85.945556640625, f: 23.887840270996094 }
if I apply this transform matrix to another rectangle (r2) the rectangle will move to the exact same position of the first one but I lost the initial x,y position before applying the rotation
How can I get these x, y values from the transform matrix ?
translate(x,y) rotate(a, x+rect.w, y+rect.height)
I've made a jsbin to illustrate my problem
https://jsbin.com/luvome/edit?html,js,output
I'm would be very grateful if you can help.
thanks.
Using this answer https://stackoverflow.com/a/15134993/2125385
I found this function
function computePosition(m, dim, angle){
var rad = angle*Math.PI/180;
var tx = m.e - dim.width/2 + Math.cos(rad)*dim.width/2 - dim.height/2*Math.sin(rad);
var ty = m.f - dim.height/2 + dim.width/2*Math.sin(rad) + dim.height/2*Math.cos(rad);
return {x:tx, y:ty}
}
I can now create a new transform matrix and apply to the rect r3.
var angle = 30;
var m = r1.transform.baseVal.consolidate().matrix;
var dim = r3.getBBox();
var pos = computePosition(m, dim, angle);
r3.transform.baseVal.initialize(
svg.createSVGTransformFromMatrix(
svg.createSVGMatrix()
.translate(pos.x, pos.y)
.translate(dim.width/2, dim.height/2)
.rotate(30)
.translate(-dim.width/2, -dim.height/2)
)
)