Displaying and formatting nested SVGs - javascript

I'm trying to display a nested SVG (path) and I'd like to manipulate the "inner" SVG so that's it's centred and scaled based on an "inner bounding box" - let's say a square 500px^2.
Here's an example but I can't get my head around the viewport and scaling to do what I need it to do.
<!DOCTYPE html>
<html>
<head>
<style>
#svg {
background:silver;
}
path {
stroke: red;
stroke-width: 0.0001;
fill: none;
}
</style>
<script src="/scripts/snippet-javascript-console.min.js?v=1"></script>
</head>
<body>
<script src="https://maps.googleapis.com/maps/api/js?v=3&libraries=geometry"></script>
<svg id="svg" height="800" width="624" viewport="0 0 800 624" preserveAspectRatio="xMinYMin meet"></svg>
<script type="text/javascript">
function latLng2point(latLng) {
return {
x: (latLng.lng + 180) * (256 / 360),
y: (256 / 2) - (256 * Math.log(Math.tan((Math.PI / 4) + ((latLng.lat * Math.PI / 180) / 2))) / (2 * Math.PI))
};
}
function poly_gm2svg(gmPaths, fx) {
var point,
gmPath,
svgPath,
svgPaths = [],
minX = 256,
minY = 256,
maxX = 0,
maxY = 0;
for (var pp = 0; pp < gmPaths.length; ++pp) {
gmPath = gmPaths[pp], svgPath = [];
for (var p = 0; p < gmPath.length; ++p) {
point = latLng2point(fx(gmPath[p]));
minX = Math.min(minX, point.x);
minY = Math.min(minY, point.y);
maxX = Math.max(maxX, point.x);
maxY = Math.max(maxY, point.y);
svgPath.push([point.x, point.y].join(','));
}
svgPaths.push(svgPath.join(' '))
}
return {
path: 'M' + svgPaths.join(' M'),
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY
};
}
function drawPoly(node, props) {
var svg = node.cloneNode(false),
g = document.createElementNS("http://www.w3.org/2000/svg", 'g'),
path = document.createElementNS("http://www.w3.org/2000/svg", 'path');
node.parentNode.replaceChild(svg, node);
path.setAttribute('d', props.path);
g.appendChild(path);
svg.appendChild(g);
svg.setAttribute('viewBox', [props.x, props.y, props.width, props.height].join(' '));
}
function init() {
for (var i = 0; i < paths.length; ++i) {
paths[i] = google.maps.geometry.encoding.decodePath(paths[i]);
}
svgProps = poly_gm2svg(paths, function (latLng) {
return {
lat: latLng.lat(),
lng: latLng.lng()
}
});
drawPoly(document.getElementById('svg'), svgProps)
}
//array with encoded paths, will be decoded later
var paths = ["ki{eFvqfiVqAWQIGEEKAYJgBVqDJ{BHa#jAkNJw#Pw#V{APs#^aABQAOEQGKoJ_FuJkFqAo#{A}#sH{DiAs#Q]?WVy#`#oBt#_CB]KYMMkB{AQEI#WT{BlE{#zAQPI#ICsCqA_BcAeCmAaFmCqIoEcLeG}KcG}A}#cDaBiDsByAkAuBqBi#y#_#o#o#kB}BgIoA_EUkAMcACa#BeBBq#LaAJe#b#uA`#_AdBcD`#iAPq#RgALqAB{#EqAyAoOCy#AmCBmANqBLqAZkB\\iCPiBJwCCsASiCq#iD]eA]y#[i#w#mAa#i#k#g#kAw#i#Ya#Q]EWFMLa#~BYpAFNpA`Aj#n#X`#V`AHh#JfB#xAMvAGZGHIDIAWOEQNcC#sACYK[MSOMe#QKKKYOs#UYQISCQ?Q#WNo#r#OHGAGCKOQ_BU}#MQGG]Io##c#FYNg#d#s#d#ODQAMOMaASs#_#a#SESAQDqBn#a#RO?KK?UBU\\kA#Y?WMo#Iy#GWQ_#WSSGg#AkABQB_Ap#_A^o#b#Q#o#IS#OHi#n#OFS?OI}#iAQMQGQC}#DOIIUK{#IUOMyBo#kASOKIQCa#L[|AgATWN[He#?QKw#FOPCh#Fx#l#TDLELKl#aAHIJEX#r#ZTDV#LENQVg#RkA#c#MeA?WFOPMf#Ej#Fj##LGHKDM?_#_#iC?a#HKRIl#NT?FCHMFW?YEYGWQa#GYBiAIq#Gq#L_BHSHK|#WJETSLQZs#z#_A~#uA^U`#G\\CRB\\Tl#p#Th#JZ^bB`#lAHLXVLDP?LGFSKiDBo#d#wBVi#R]VYVE\\#`#Lh#Fh#CzAk#RSDQA]GYe#eAGWSiBAWBWBIJORK`#KPOPSTg#h#}Ad#o#F[E_#EGMKUGmAEYGMIMYKs#?a#J}##_BD_#HQJMx#e#LKHKHWAo#UoAAWFmAH}#?w#C[YwAAc#HSNM|Ao#rA}#zAq#`#a#j#eAxAuBXQj#MXSR[b#gAFg#?YISOGaAHi#Xw#v#_#d#WRSFqARUHQJc#d#m#`A[VSFUBcAEU#WFULUPa#v#Y~#UrBc#dBI~#?l#P~ABt#N`HEjA]zAEp##p#TrBCl#CTQb#k#dAg#jAU^KJYLK#k#A[Js#d#a#b#]RgBl#[FMAw#[]G]?m#D_#F]P[Vu#t#[TMF_#Do#E_##q#P]PWZUZw#vAkAlAGJOj#IlAMd#OR{#p#a#d#sBpD]v#a#`Aa#n#]TODgBVk#Pe#^cBfBc#Rs#La#RSPm#|#wCpDS^Wp#QZML{#l#qBbCYd#k#lAIVCZBZNTr#`#RRHZANIZQPKDW#e#CaASU?I#YTKRQx##\\VmALYRQLCL?v#P|#D\\GJEFKDM#OCa#COOYIGm#YMUCM#]JYr#uAx#kAt#}#jAeAPWbAkBj#s#bAiAz#oAj#m#VQlAc#VQ~#aA`Au#p#Q`AIv#MZORUV_#p#iB|AoCh#q#dAaANUNWH[N{AJ[^m#t#_Av#wA\\a#`#W`#In#Al#B^E`#Wl#u#\\[VQ\\K`#Eb#?R#dAZP#d#CRExAs#\\Yt#{#LG\\MjAATINOXo#d#kAl#_AHYBOCe#QiBCm#Fq#\\wADo#AyGEeBWuB#YHu#Tu#Lk#VcCTo#d#aA\\WJE`#G~#FP?VI\\U~#sANO`#SfAMj#U\\WjAsAXS`#UNENALBHFFL?^Ml#Uj#]b#q#RUJSPkChEc#XcAb#sA|#]PaA\\OJKNER?TDTNj#Jn#?p#OfC#ZR`B#VCV_#n#{#l#WbACv#OlABnAPl#LNNHbBBNBLFFJ#^GLg#x#i#|AMP[X}#XOJKPET?l#LhAFXp#fBDRCd#S\\_#Ps#PQ#}A]S?QDe#V]b#MR[fAKt#ErAF~CANILYDKGIKe#{#Yy#e#sB[gA[c#e#YUCU?WBUHUNQPq#`AiArAMV[^e#Zc#JQJKNMz#?r#Bb#PfAAfA#VVbADn#E`#KHSEe#SMAKDKFM\\^dDCh#m#LoAQ_##MFOZLfBEl#QbASd#KLQBOAaAc#QAQ#QHc#v#ONMJOBOCg#c#]O[EMBKFGL?RHv#ARERGNe#h#{#h#WVGNDt#JLNFPFz#LdBf#f#PJNHPF`ADPJJJDl#I`#B^Tp#bALJNDNALIf#i#PGPCt#DNE`#Uv#[dAw#RITGRCtAARBPJLPJRZxB?VEX_#vAAR?RDNHJJBh#UnBm#h#IRDRJNNJPNbBFRJLLBLCzAmAd#Uf#Gf#?P#PFJNHPFTH`BDTHNJJJ#LG`#m#^YPER#RDPHNNJRLn#HRLN^VNPHTFX#\\UlDFb#FHh#NP#HKPsB?}ASkCQ{#[y#q#}#cA{#KOCQDa#t#{CFGJCf#Nl#ZtA~#r#p#`#h#rAxBd#rA\\fARdAPjANrB?f#AtBCd#QfBkAjJOlBChA?rBFrBNlBdAfKFzAC~#Iz#Mz#Sv#s#jBmAxBi#hAWt#Sv#Qx#O`BA`#?dAPfBVpAd#`BfBlFf#fBdA~Cr#pAz#fApBhBjAt#H?IL?FBFJLx#^lHvDvh#~XnElCbAd#pGhDbAb#nAr#`Ad#`GhDnBbAxCbBrWhNJJDPARGP_#t#Qh#]pAUtAoA`Ny#jJApBBNFLJFJBv#Hb#HBF?\\"];
init();
</script>
</body>
</html>
The example is using an encoded google polyline and this path is likely to change - I'd like to force any path to fit within the confines of a 500px square - centred.
Any pointers on how I can do this? I know SVG is a bit tricky with absolute positioning.
This is vanilla SVG but I would consider using some js libraries if they can handle positioning better (any suggestions?)
EDIT: Adding a mockup of what I'm trying to do

From what I understand, you want to have a 624*800px SVG, and in its middle an area of 500*500px in which to draw your path. To do that you can place a nested SVG of that size right where you want it. As you know the size of the outer SVG, you can easily compute the position by hand. In the markup, leave off the viewBox.
For the outer SVG you has some errors in xour code: the attribute name is viewBox, and for the values, width coems before height:
viewBox="0 0 624 800"
Later, when adding the path to that inner <svg> element (no <g> required), set its viewBox using the values from the path properties. That's all.
function latLng2point(latLng) {
return {
x: (latLng.lng + 180) * (256 / 360),
y: (256 / 2) - (256 * Math.log(Math.tan((Math.PI / 4) + ((latLng.lat * Math.PI / 180) / 2))) / (2 * Math.PI))
};
}
function poly_gm2svg(gmPaths, fx) {
var point,
gmPath,
svgPath,
svgPaths = [],
minX = 256,
minY = 256,
maxX = 0,
maxY = 0;
for (var pp = 0; pp < gmPaths.length; ++pp) {
gmPath = gmPaths[pp], svgPath = [];
for (var p = 0; p < gmPath.length; ++p) {
point = latLng2point(fx(gmPath[p]));
minX = Math.min(minX, point.x);
minY = Math.min(minY, point.y);
maxX = Math.max(maxX, point.x);
maxY = Math.max(maxY, point.y);
svgPath.push([point.x, point.y].join(','));
}
svgPaths.push(svgPath.join(' '))
}
return {
path: 'M' + svgPaths.join(' M'),
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY
};
}
function drawPoly(svg, props) {
path = document.createElementNS("http://www.w3.org/2000/svg", 'path');
path.setAttribute('d', props.path);
svg.setAttribute('viewBox', [props.x, props.y, props.width, props.height].join(' '));
svg.appendChild(path);
}
function init() {
for (var i = 0; i < paths.length; ++i) {
paths[i] = google.maps.geometry.encoding.decodePath(paths[i]);
}
svgProps = poly_gm2svg(paths, function (latLng) {
return {
lat: latLng.lat(),
lng: latLng.lng()
}
});
drawPoly(document.querySelector('#svg svg'), svgProps)
}
//array with encoded paths, will be decoded later
var paths = ["ki{eFvqfiVqAWQIGEEKAYJgBVqDJ{BHa#jAkNJw#Pw#V{APs#^aABQAOEQGKoJ_FuJkFqAo#{A}#sH{DiAs#Q]?WVy#`#oBt#_CB]KYMMkB{AQEI#WT{BlE{#zAQPI#ICsCqA_BcAeCmAaFmCqIoEcLeG}KcG}A}#cDaBiDsByAkAuBqBi#y#_#o#o#kB}BgIoA_EUkAMcACa#BeBBq#LaAJe#b#uA`#_AdBcD`#iAPq#RgALqAB{#EqAyAoOCy#AmCBmANqBLqAZkB\\iCPiBJwCCsASiCq#iD]eA]y#[i#w#mAa#i#k#g#kAw#i#Ya#Q]EWFMLa#~BYpAFNpA`Aj#n#X`#V`AHh#JfB#xAMvAGZGHIDIAWOEQNcC#sACYK[MSOMe#QKKKYOs#UYQISCQ?Q#WNo#r#OHGAGCKOQ_BU}#MQGG]Io##c#FYNg#d#s#d#ODQAMOMaASs#_#a#SESAQDqBn#a#RO?KK?UBU\\kA#Y?WMo#Iy#GWQ_#WSSGg#AkABQB_Ap#_A^o#b#Q#o#IS#OHi#n#OFS?OI}#iAQMQGQC}#DOIIUK{#IUOMyBo#kASOKIQCa#L[|AgATWN[He#?QKw#FOPCh#Fx#l#TDLELKl#aAHIJEX#r#ZTDV#LENQVg#RkA#c#MeA?WFOPMf#Ej#Fj##LGHKDM?_#_#iC?a#HKRIl#NT?FCHMFW?YEYGWQa#GYBiAIq#Gq#L_BHSHK|#WJETSLQZs#z#_A~#uA^U`#G\\CRB\\Tl#p#Th#JZ^bB`#lAHLXVLDP?LGFSKiDBo#d#wBVi#R]VYVE\\#`#Lh#Fh#CzAk#RSDQA]GYe#eAGWSiBAWBWBIJORK`#KPOPSTg#h#}Ad#o#F[E_#EGMKUGmAEYGMIMYKs#?a#J}##_BD_#HQJMx#e#LKHKHWAo#UoAAWFmAH}#?w#C[YwAAc#HSNM|Ao#rA}#zAq#`#a#j#eAxAuBXQj#MXSR[b#gAFg#?YISOGaAHi#Xw#v#_#d#WRSFqARUHQJc#d#m#`A[VSFUBcAEU#WFULUPa#v#Y~#UrBc#dBI~#?l#P~ABt#N`HEjA]zAEp##p#TrBCl#CTQb#k#dAg#jAU^KJYLK#k#A[Js#d#a#b#]RgBl#[FMAw#[]G]?m#D_#F]P[Vu#t#[TMF_#Do#E_##q#P]PWZUZw#vAkAlAGJOj#IlAMd#OR{#p#a#d#sBpD]v#a#`Aa#n#]TODgBVk#Pe#^cBfBc#Rs#La#RSPm#|#wCpDS^Wp#QZML{#l#qBbCYd#k#lAIVCZBZNTr#`#RRHZANIZQPKDW#e#CaASU?I#YTKRQx##\\VmALYRQLCL?v#P|#D\\GJEFKDM#OCa#COOYIGm#YMUCM#]JYr#uAx#kAt#}#jAeAPWbAkBj#s#bAiAz#oAj#m#VQlAc#VQ~#aA`Au#p#Q`AIv#MZORUV_#p#iB|AoCh#q#dAaANUNWH[N{AJ[^m#t#_Av#wA\\a#`#W`#In#Al#B^E`#Wl#u#\\[VQ\\K`#Eb#?R#dAZP#d#CRExAs#\\Yt#{#LG\\MjAATINOXo#d#kAl#_AHYBOCe#QiBCm#Fq#\\wADo#AyGEeBWuB#YHu#Tu#Lk#VcCTo#d#aA\\WJE`#G~#FP?VI\\U~#sANO`#SfAMj#U\\WjAsAXS`#UNENALBHFFL?^Ml#Uj#]b#q#RUJSPkChEc#XcAb#sA|#]PaA\\OJKNER?TDTNj#Jn#?p#OfC#ZR`B#VCV_#n#{#l#WbACv#OlABnAPl#LNNHbBBNBLFFJ#^GLg#x#i#|AMP[X}#XOJKPET?l#LhAFXp#fBDRCd#S\\_#Ps#PQ#}A]S?QDe#V]b#MR[fAKt#ErAF~CANILYDKGIKe#{#Yy#e#sB[gA[c#e#YUCU?WBUHUNQPq#`AiArAMV[^e#Zc#JQJKNMz#?r#Bb#PfAAfA#VVbADn#E`#KHSEe#SMAKDKFM\\^dDCh#m#LoAQ_##MFOZLfBEl#QbASd#KLQBOAaAc#QAQ#QHc#v#ONMJOBOCg#c#]O[EMBKFGL?RHv#ARERGNe#h#{#h#WVGNDt#JLNFPFz#LdBf#f#PJNHPF`ADPJJJDl#I`#B^Tp#bALJNDNALIf#i#PGPCt#DNE`#Uv#[dAw#RITGRCtAARBPJLPJRZxB?VEX_#vAAR?RDNHJJBh#UnBm#h#IRDRJNNJPNbBFRJLLBLCzAmAd#Uf#Gf#?P#PFJNHPFTH`BDTHNJJJ#LG`#m#^YPER#RDPHNNJRLn#HRLN^VNPHTFX#\\UlDFb#FHh#NP#HKPsB?}ASkCQ{#[y#q#}#cA{#KOCQDa#t#{CFGJCf#Nl#ZtA~#r#p#`#h#rAxBd#rA\\fARdAPjANrB?f#AtBCd#QfBkAjJOlBChA?rBFrBNlBdAfKFzAC~#Iz#Mz#Sv#s#jBmAxBi#hAWt#Sv#Qx#O`BA`#?dAPfBVpAd#`BfBlFf#fBdA~Cr#pAz#fApBhBjAt#H?IL?FBFJLx#^lHvDvh#~XnElCbAd#pGhDbAb#nAr#`Ad#`GhDnBbAxCbBrWhNJJDPARGP_#t#Qh#]pAUtAoA`Ny#jJApBBNFLJFJBv#Hb#HBF?\\"];
init();
#svg {
background:silver;
}
path {
stroke: red;
stroke-width: 0.0001;
fill: none;
}
<script src="https://maps.googleapis.com/maps/api/js?v=3&libraries=geometry"></script>
<svg id="svg" height="800" width="624" viewBox="0 0 624 800" preserveAspectRatio="xMinYMin meet">
<svg x="62" y="150" width="500" height="500"></svg>
</svg>

Related

SVG path with stroke fills the svg element

I have a set of coordinates that can be used to create a polygon in google maps. I used the mercantor-projection formula described in this answer to convert it into a set of points (x,y) that can be used in an svg . The conversion was successful and it was rendered properly.
The snippet below renders well without the setting the stroke property in the element. If you un-comment line 29 where it sets the stroke property to red, it will fill up the entire svg element instead of outlining the element with red which highlights the main problem of this post.
const COORDINATES = [[-86.917595,32.664169],[-86.918817,32.644278],[-86.898304,32.567687],[-86.90675,32.537298],[-86.890829,32.513742],[-86.878989,32.487323],[-86.885203,32.482438],[-86.870212,32.468974],[-86.860056,32.450861],[-86.86709,32.439188],[-86.849898,32.438325],[-86.824394,32.424725],[-86.845448,32.415416],[-86.843758,32.400416],[-86.827244,32.378816],[-86.815399,32.370821],[-86.807378,32.354356],[-86.814912,32.340803],[-86.820921,32.33324],[-86.816107,32.30997],[-86.798268,32.308632],[-86.773163,32.340728],[-86.780447,32.3686],[-86.778365,32.394601],[-86.749981,32.389105],[-86.727181,32.404497],[-86.717897,32.402814],[-86.719028,32.372059],[-86.711337,32.360767],[-86.683537,32.353395],[-86.655597,32.376147],[-86.653419,32.397247],[-86.618,32.405717],[-86.613453,32.398584],[-86.614841,32.374266],[-86.595335,32.361345],[-86.581873,32.375019],[-86.570551,32.375006],[-86.542537,32.363517],[-86.532531,32.338775],[-86.496774,32.344437],[-86.491902,32.364327],[-86.463564,32.377288],[-86.461277,32.403473],[-86.456273,32.405837],[-86.444721,32.399841],[-86.411172,32.409937],[-86.412229,32.528746],[-86.412446,32.579324],[-86.413116,32.707386],[-86.524434,32.707058],[-86.714219,32.705694],[-86.71339,32.661732],[-86.771532,32.660717],[-86.816574,32.660117],[-86.917595,32.664169]];
const getPoint = ([x, y]) => {
return {
x: (x + 180) * (256 / 360),
y: (256 / 2) - (256 * Math.log(Math.tan((Math.PI / 4) + ((y * Math.PI / 180) / 2))) / (2 * Math.PI))
};
};
const svg = document.querySelector('svg');
const points = [];
let minX = 256;
let minY = 256;
let maxX = 0;
let maxY = 0;
for(const coordinate of COORDINATES) {
const point = getPoint(coordinate);
minX = Math.min(minX, point.x);
minY = Math.min(minY, point.y);
maxX = Math.max(maxX, point.x);
maxY = Math.max(maxY, point.y);
points.push(`${point.x},${point.y}`);
}
const g = document
.createElementNS("http://www.w3.org/2000/svg", 'g');
const path = document
.createElementNS("http://www.w3.org/2000/svg", 'path');
svg.setAttribute(
'viewBox',
[minX, minY, maxX - minX, maxY - minY].join(' ')
);
path.setAttribute('d', 'M' + points.join(' ') + 'z');
path.setAttribute('fill', 'blue');
//path.setAttribute('stroke', 'red');
g.appendChild(path);
svg.appendChild(g);
<svg height="400" width="400" preserveAspectRatio="xMinYMin meet"></svg>
Why does setting the stroke property fills the SVG element with the specified stroke color?
It would be great if anyone can point out how to solve the problem of the snippet above.
The problem is related to the scaling of your SVG. Setting the stroke width to a small value like 0.0025 fixes it.
const COORDINATES = [[-86.917595,32.664169],[-86.918817,32.644278],[-86.898304,32.567687],[-86.90675,32.537298],[-86.890829,32.513742],[-86.878989,32.487323],[-86.885203,32.482438],[-86.870212,32.468974],[-86.860056,32.450861],[-86.86709,32.439188],[-86.849898,32.438325],[-86.824394,32.424725],[-86.845448,32.415416],[-86.843758,32.400416],[-86.827244,32.378816],[-86.815399,32.370821],[-86.807378,32.354356],[-86.814912,32.340803],[-86.820921,32.33324],[-86.816107,32.30997],[-86.798268,32.308632],[-86.773163,32.340728],[-86.780447,32.3686],[-86.778365,32.394601],[-86.749981,32.389105],[-86.727181,32.404497],[-86.717897,32.402814],[-86.719028,32.372059],[-86.711337,32.360767],[-86.683537,32.353395],[-86.655597,32.376147],[-86.653419,32.397247],[-86.618,32.405717],[-86.613453,32.398584],[-86.614841,32.374266],[-86.595335,32.361345],[-86.581873,32.375019],[-86.570551,32.375006],[-86.542537,32.363517],[-86.532531,32.338775],[-86.496774,32.344437],[-86.491902,32.364327],[-86.463564,32.377288],[-86.461277,32.403473],[-86.456273,32.405837],[-86.444721,32.399841],[-86.411172,32.409937],[-86.412229,32.528746],[-86.412446,32.579324],[-86.413116,32.707386],[-86.524434,32.707058],[-86.714219,32.705694],[-86.71339,32.661732],[-86.771532,32.660717],[-86.816574,32.660117],[-86.917595,32.664169]];
const getPoint = ([x, y]) => {
return {
x: (x + 180) * (256 / 360),
y: (256 / 2) - (256 * Math.log(Math.tan((Math.PI / 4) + ((y * Math.PI / 180) / 2))) / (2 * Math.PI))
};
};
const svg = document.querySelector('svg');
const points = [];
let minX = 256;
let minY = 256;
let maxX = 0;
let maxY = 0;
for(const coordinate of COORDINATES) {
const point = getPoint(coordinate);
minX = Math.min(minX, point.x);
minY = Math.min(minY, point.y);
maxX = Math.max(maxX, point.x);
maxY = Math.max(maxY, point.y);
points.push(`${point.x},${point.y}`);
}
const g = document
.createElementNS("http://www.w3.org/2000/svg", 'g');
const path = document
.createElementNS("http://www.w3.org/2000/svg", 'path');
svg.setAttribute(
'viewBox',
[minX, minY, maxX - minX, maxY - minY].join(' ')
);
path.setAttribute('d', 'M' + points.join(' ') + 'z');
path.setAttribute('fill', 'blue');
path.setAttribute('stroke', 'red');
path.setAttribute('stroke-width', '.0025');
g.appendChild(path);
svg.appendChild(g);
<svg height="400" width="400" preserveAspectRatio="xMinYMin meet"></svg>

SVG object - how to zoom and drag it properly?

I have an SVG map and simple plugin which adds zoom and drag functionalities.
<svg>
<g class="main-container draggable" transform="matrix(1 0 0 1 0 0)">
<path id="AT-1" title="Burgenland" class="land" d=".../>
</g>
</svg>
const maxScale = 5,
minScale = 0.15;
var selected,
scale = 1,
svg = document.querySelector('svg');
function beginDrag(e) {
e.stopPropagation();
let target = e.target;
if (target.classList.contains('draggable')) {
selected = target;
} else {
selected = document.querySelector('.main-container');
}
selected.dataset.startMouseX = e.clientX;
selected.dataset.startMouseY = e.clientY;
}
function drag(e) {
if (!selected) return;
e.stopPropagation();
let startX = parseFloat(selected.dataset.startMouseX),
startY = parseFloat(selected.dataset.startMouseY),
dx = (e.clientX - startX),
dy = (e.clientY - startY);
if (selected.classList.contains('draggable')) {
let selectedBox = selected.getBoundingClientRect(),
boundaryBox = selected.parentElement.getBoundingClientRect();
if (selectedBox.right + dx > boundaryBox.right) {
dx = (boundaryBox.right - selectedBox.right);
} else if (selectedBox.left + dx < boundaryBox.left) {
dx = (boundaryBox.left - selectedBox.left);
}
if (selectedBox.bottom + dy > boundaryBox.bottom) {
dy = (boundaryBox.bottom - selectedBox.bottom);
}
else if (selectedBox.top + dy < boundaryBox.top) {
dy = (boundaryBox.top - selectedBox.top);
}
}
let currentMatrix = selected.transform.baseVal.consolidate().matrix,
newMatrix = currentMatrix.translate(dx / scale, dy / scale),
transform = svg.createSVGTransformFromMatrix(newMatrix);
selected.transform.baseVal.initialize(transform);
selected.dataset.startMouseX = dx + startX;
selected.dataset.startMouseY = dy + startY;
}
function endDrag(e) {
e.stopPropagation();
if (selected) {
selected = undefined;
}
}
function zoom(e) {
e.stopPropagation();
e.preventDefault();
let delta = e.wheelDelta,
container = document.querySelector('svg .main-container'),
scaleStep = delta > 0 ? 1.25 : 0.8;
if (scale * scaleStep > maxScale) {
scaleStep = maxScale / scale;
}
if (scale * scaleStep < minScale) {
scaleStep = minScale / scale;
}
scale *= scaleStep;
let box = svg.getBoundingClientRect();
let point = svg.createSVGPoint();
point.x = e.clientX - box.left;
point.y = e.clientY - box.top;
let currentZoomMatrix = container.getCTM();
point = point.matrixTransform(currentZoomMatrix.inverse());
let matrix = svg.createSVGMatrix()
.translate(point.x, point.y)
.scale(scaleStep)
.translate(-point.x, -point.y);
let newZoomMatrix = currentZoomMatrix.multiply(matrix);
container.transform.baseVal.initialize(svg.createSVGTransformFromMatrix(newZoomMatrix));
console.log("scale", scale);
let t = newZoomMatrix;
console.log("zoomMatrix", t.a, t.b, t.c, t.d, t.e, t.f);
}
document.querySelector('svg').addEventListener('mousedown', beginDrag);
document.querySelector('svg').addEventListener('mousewheel', zoom);
svg.addEventListener('mousemove', drag);
window.addEventListener('mouseup', endDrag);
At first sight, it works fine, however, it behaves strangely in some situations.
For example - if you zoom out I can freely drag it to any directions without any problem.
But if I zoom in to the scale that part of the map exceeds the parent element, any attempt to move it causes a jump of the entire map and blocks this functionality.
The second thing is - right now I can only move the map in the borders of SVG element and I want to have a possibility to drag it out outside it in the same way it works here: https://www.amcharts.com/svg-maps/?map=austria
Here is a snippet with my code: https://jsfiddle.net/marektchas/qo1eynag/3/
Posting here easy way for pan zoom functionality.
var svgCanvas = document.getElementById("canvas");
var viewPort = document.getElementById("viewport");
var drag = false;
var offset = { x: 0, y: 0 };
var factor = .1;
var matrix = new DOMMatrix();
svgCanvas.addEventListener('pointerdown', function (event) {
drag = true;
offset = { x: event.offsetX, y: event.offsetY };
});
svgCanvas.addEventListener('pointermove', function (event) {
if (drag) {
var tx = event.offsetX - offset.x;
var ty = event.offsetY - offset.y;
offset = {
x: event.offsetX,
y: event.offsetY
};
matrix.preMultiplySelf(new DOMMatrix()
.translateSelf(tx, ty));
viewPort.style.transform = matrix.toString();
}
});
svgCanvas.addEventListener('pointerup', function (event) {
drag = false;
});
svgCanvas.addEventListener('wheel', function (event) {
var zoom = event.deltaY > 0 ? -1 : 1;
var scale = 1 + factor * zoom;
offset = {
x: event.offsetX,
y: event.offsetY
};
matrix.preMultiplySelf(new DOMMatrix()
.translateSelf(offset.x, offset.y)
.scaleSelf(scale, scale)
.translateSelf(-offset.x, -offset.y));
viewPort.style.transform = matrix.toString();
});
html,
body {
height: 100%;
margin: 0;
box-sizing: border-box;
font-size: 62.5%;
}
body{
display: flex;
}
#around{
display: flex;
width: 100%;
border: 1px dashed orange;
}
#canvas{
flex: 1;
height: auto;
}
<div id="around">
<svg id="canvas" style="border: 1px solid blue;">
<g id="viewport">
<rect x="100" y="100" width="200" height="200" fill="red"/>
</g>
</svg>
</div>
It seems I found the solution however, I'm not quite sure how does it exactly work.
It works as expected if I remove .draggable class from the g element

Convert Google Maps polyline to SVG

I am using the JavaScript from a previous question (Convert a Google Maps polygon path to an SVG path) to display an SVG from a list of coordinates. See JsFiddle: http://jsfiddle.net/londonfed/9xhsL39u/51/
However, this does not render properly (it shows a red blob). It should show a straight line as illustrated in this screenshot:
The above image was taken from https://developers.google.com/maps/documentation/utilities/polylineutility?csw=1
You can copy paste the polyline inside the encoded polyline input field to see how the SVG should render:
ofkyHluWg#?EyDQi#}ByFJMcAgCeAyCeAiDu#iC`#_#??a#^IY{#{Cu#aDSg#KQSq#m#kCYgAIc#QJKYc#{#W{#Ga#C_#SkBQcA??GaAMuASwAS_BGo#GWQ]c#i#GMKWGCCAUuA[sBSaBKm#E]OaB
Any ideas much appreciated.
There are a couple of things wrong. Mainly:
Your coordinates are tiny, so a default stroke-width of "1" is way too big. 0.0001 is more the scale you want.
You want an open path, not a closed one, so don't include a "z".
function latLng2point(latLng) {
return {
x: (latLng.lng + 180) * (256 / 360),
y: (256 / 2) - (256 * Math.log(Math.tan((Math.PI / 4) + ((latLng.lat * Math.PI / 180) / 2))) / (2 * Math.PI))
};
}
function poly_gm2svg(gmPaths, fx) {
var point,
gmPath,
svgPath,
svgPaths = [],
minX = 256,
minY = 256,
maxX = 0,
maxY = 0;
for (var pp = 0; pp < gmPaths.length; ++pp) {
gmPath = gmPaths[pp], svgPath = [];
for (var p = 0; p < gmPath.length; ++p) {
point = latLng2point(fx(gmPath[p]));
minX = Math.min(minX, point.x);
minY = Math.min(minY, point.y);
maxX = Math.max(maxX, point.x);
maxY = Math.max(maxY, point.y);
svgPath.push([point.x, point.y].join(','));
}
svgPaths.push(svgPath.join(' '))
}
return {
path: 'M' + svgPaths.join(' M'),
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY
};
}
function drawPoly(node, props) {
var svg = node.cloneNode(false),
g = document.createElementNS("http://www.w3.org/2000/svg", 'g'),
path = document.createElementNS("http://www.w3.org/2000/svg", 'path');
node.parentNode.replaceChild(svg, node);
path.setAttribute('d', props.path);
g.appendChild(path);
svg.appendChild(g);
svg.setAttribute('viewBox', [props.x, props.y, props.width, props.height].join(' '));
}
function init() {
for (var i = 0; i < paths.length; ++i) {
paths[i] = google.maps.geometry.encoding.decodePath(paths[i]);
}
svgProps = poly_gm2svg(paths, function (latLng) {
return {
lat: latLng.lat(),
lng: latLng.lng()
}
});
drawPoly(document.getElementById('svg'), svgProps)
}
//array with encoded paths, will be decoded later
var paths = ["ofkyHluWg#?EyDQi#}ByFJMcAgCeAyCeAiDu#iC`#_#??a#^IY{#{Cu#aDSg#KQSq#m#kCYgAIc#QJKYc#{#W{#Ga#C_#SkBQcA??GaAMuASwAS_BGo#GWQ]c#i#GMKWGCCAUuA[sBSaBKm#E]OaB"];
init();
#svg {
background:silver;
}
path {
stroke: red;
stroke-width: 0.0001;
fill: none;
}
<script src="https://maps.googleapis.com/maps/api/js?v=3&libraries=geometry"></script>
<svg id="svg" height="400" width="400" viewport="0 0 400 400" preserveAspectRatio="xMinYMin meet"></svg>

Collision between two elements with rotating

var keys = new Array();
var direction;
var direction;
var iNr = 0;
$(document).ready(function(){
looper();
$("#demo1").css("margin-top", 400 + "px");
$("#demo2").css("margin-left", 380 + "px");
myFunction();
});
function myFunction()
{
iNr = iNr + 0.5;
$("#main").css("transition","all 0.1s");
$("#main").css("transform","rotate(" + iNr + "deg)");
setTimeout(function()
{
myFunction();
}, 50);
}
function looper()
{
var p =$("#circle");
var offset = p.offset();
var t =$(".red");
var roffset = t.offset();
var rect1 = {x: offset.left, y: offset.top, width: p.width(), height: p.height()}
var rect2 = {x: roffset.left, y: roffset.top, width: t.width(), height: t.height()}
if (rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.height + rect1.y > rect2.y) {
console.log("now");
}
if(direction == "left")
{
if(offset.left - 50 > 0)
{
$("#circle").css("left", ($("#circle").position().left - 2) + "px");
}
}
if(direction == "up")
{
if(offset.top - 50 > 0)
{
$("#circle").css("top", ($("#circle").position().top - 2) + "px");
}
}
if(direction == "right")
{
if((offset.left + 50) < $(window).width())
{
$("#circle").css("left", ($("#circle").position().left + 2) + "px");
}
}
if(direction == "down")
{
if((offset.top + 50) < $(window).height())
{
$("#circle").css("top", ($("#circle").position().top + 2) + "px");
}
}
ID=window.setTimeout("looper();", 1);
}
$(document).keyup(function(event) {
if (event.keyCode == 37)
{
var index = keys.indexOf("37");
keys.splice(index, 1);
direction = "";
}
if (event.keyCode == 38)
{
var index = keys.indexOf("38");
keys.splice(index, 1);
direction = "";
}
if (event.keyCode == 39)
{
var index = keys.indexOf("39");
keys.splice(index, 1);
direction = "";
}
if (event.keyCode == 40)
{
var index = keys.indexOf("40");
keys.splice(index, 1);
direction = "";
}
});
$(document).keydown(function(event) {
if (event.keyCode == 37)
{
keys.push("37");
direction = "left";
}
if (event.keyCode == 38)
{
keys.push("38");
direction = "up";
}
if (event.keyCode == 39)
{
keys.push("39");
direction = "right";
}
if (event.keyCode == 40)
{
keys.push("40");
direction = "down";
}
});
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>test</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body style="background-color:black; overflow-y:scroll;">
<div style="width:400px; margin-left:500px; height:400px;" id="main">
<div id="demo1" style="width:400px; height:20px; background-color:red; position:absolute;" class="red test all"></div>
<div id="demo2" style="width:20px; height:400px; background-color:yellow; position:absolute;" class="test all"></div>
<div id="demo3" style="width:400px; height:20px; background-color:blue; position:absolute;" class="test all"></div>
<div id="demo4" style="width:20px; height:400px; background-color:green; position:absolute;" class="test all"></div>
</div>
<div style="width:25px; height:25px; background-color:white; position:absolute; border-radius:50%;" id="circle"></div>
</body>
</html>
I have programmed a game.
In this game my function checks, whether there is a collision between div1 and div2.
Or if they are overlapping or so... how you want to spell it.
Without a rotation everything is ok.
But now i have a problem.
I want to rotate div2 with transform:rotate(Xdeg);
but if I do this my calculation for the collision dosen't work.
I use this:
var rect1 = {x: 5, y: 5, width: 50, height: 50}
var rect2 = {x: 20, y: 10, width: 10, height: 10}
if (rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.height + rect1.y > rect2.y) {
// collision detected!
}
do you have any ideas to solve this problem?
Thanks for helping :-)
There are several ways to do this. This example just guides you of how it could be done with rectangles.
These are the steps that are done here:
You have to calculate the position of all rotated corners of all rectangles that you want to check whether they are being collided. To get these rotated corners, you can use several methods. In this example 2d-vectors and a 2d-rotation-matrix are used:
a vector that has its origin in the center of a rectangle and directs to the top-left-corner(x,y) of a rectangle:
var center = {
x: x + width / 2,
y: y + height / 2
};
var vector = {
x: (x - center.x),
y: (y - center.y)
};
multiply this vector with a rotation-matrix to rotate this vector:
// modified sin function to calulcate sin in the unit degrees instead of radians
function sin(x) {
return Math.sin(x / 180 * Math.PI);
}
// modified cos function
function cos(x) {
return Math.cos(x / 180 * Math.PI);
}
var rotationMatrix = [[cos(angle), -sin(angle)],[sin(angle), cos(angle)]];
var rotatedVector = {
x: vector.x * rotationMatrix[0][0] + vector.y * rotationMatrix[0][1],
y: vector.x * rotationMatrix[1][0] + vector.y * rotationMatrix[1][1]
};
And finally get the rotated top-left-corner, you can start from the center of a rectangle and go to where the rotated vector points to. This is where top-left-corner is located after rotation:
{
x: (center.x + rotatedVector.x),
y: (center.y + rotatedVector.y)
}
All the steps described above are done by getRotatedTopLeftCornerOfRect and must be done with all other corners as well. Before the location of the next
corner (right-top) can be calculated next vector must be calulcated that points to this corner. To get the next vector that points to the top-right-corner the angle between the first vector (left-top) and the second vector (right-top) is calculated. The third vector points to the right-bottom-corner when its angle is incremended by the first angle and the second angle and the fourth vector is rotated by an angle that is summed up the first, second and third angle. All of this is done in the setCorners-method and this image shows this process partly:
To detect a collision there are tons of algorithms. In this example the Point in polygon algorithm is used to check each rotated corner of a rectangle whether a corner is with the border or within another rectangle, if so, then the method isCollided returns true. The Point in polygon algorithm is used in pointInPoly and can also be found here.
Combining all of the steps described above was tricky, but it works with all rectangles of all sizes and the best of all you can test it right here without a library by clicking on "Run code snippet".
(tested browsers: FF 50.1.0, IE:10-EDGE, Chrome:55.0.2883.87 m):
var Rectangle = (function () {
function sin(x) {
return Math.sin(x / 180 * Math.PI);
}
function cos(x) {
return Math.cos(x / 180 * Math.PI);
}
function getVectorLength(x, y, width, height){
var center = {
x: x + width / 2,
y: y + height / 2
};
//console.log('center: ',center);
var vector = {
x: (x - center.x),
y: (y - center.y)
};
return Math.sqrt(vector.x*vector.x+vector.y*vector.y);
}
function getRotatedTopLeftCornerOfRect(x, y, width, height, angle) {
var center = {
x: x + width / 2,
y: y + height / 2
};
//console.log('center: ',center);
var vector = {
x: (x - center.x),
y: (y - center.y)
};
//console.log('vector: ',vector);
var rotationMatrix = [[cos(angle), -sin(angle)],[sin(angle), cos(angle)]];
//console.log('rotationMatrix: ',rotationMatrix);
var rotatedVector = {
x: vector.x * rotationMatrix[0][0] + vector.y * rotationMatrix[0][1],
y: vector.x * rotationMatrix[1][0] + vector.y * rotationMatrix[1][1]
};
//console.log('rotatedVector: ',rotatedVector);
return {
x: (center.x + rotatedVector.x),
y: (center.y + rotatedVector.y)
};
}
function getOffset(el) {
var _x = 0;
var _y = 0;
while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return {
top: _y,
left: _x
};
}
function pointInPoly(verties, testx, testy) {
var i,
j,
c = 0
nvert = verties.length;
for (i = 0, j = nvert - 1; i < nvert; j = i++) {
if (((verties[i].y > testy) != (verties[j].y > testy)) && (testx < (verties[j].x - verties[i].x) * (testy - verties[i].y) / (verties[j].y - verties[i].y) + verties[i].x))
c = !c;
}
return c;
}
function Rectangle(htmlElement, width, height, angle) {
this.htmlElement = htmlElement;
this.width = width;
this.height = height;
this.setCorners(angle);
}
function testCollision(rectangle) {
var collision = false;
this.getCorners().forEach(function (corner) {
var isCollided = pointInPoly(rectangle.getCorners(), corner.x, corner.y);
if (isCollided) collision = true;
});
return collision;
}
function checkRectangleCollision(rect, rect2) {
if (testCollision.call(rect, rect2)) return true;
else if (testCollision.call(rect2, rect)) return true;
return false;
}
function getAngleForNextCorner(anc,vectorLength) {
var alpha = Math.acos(anc/vectorLength)*(180 / Math.PI);
return 180 - alpha*2;
}
Rectangle.prototype.setCorners = function (angle) {
this.originalPos = getOffset(this.htmlElement);
this.leftTopCorner = getRotatedTopLeftCornerOfRect(this.originalPos.left, this.originalPos.top, this.width, this.height, angle);
var vecLength = getVectorLength(this.originalPos.left, this.originalPos.top, this.width, this.height);
//console.log('vecLength: ',vecLength);
angle = angle+getAngleForNextCorner(this.width/2, vecLength);
//console.log('angle: ',angle);
this.rightTopCorner = getRotatedTopLeftCornerOfRect(this.originalPos.left, this.originalPos.top, this.width, this.height, angle);
angle = angle+getAngleForNextCorner(this.height/2, vecLength);
//console.log('angle: ',angle);
this.rightBottomCorner = getRotatedTopLeftCornerOfRect(this.originalPos.left, this.originalPos.top, this.width, this.height, angle);
angle = angle+getAngleForNextCorner(this.width/2, vecLength);
//console.log('angle: ',angle);
this.leftBottomCorner = getRotatedTopLeftCornerOfRect(this.originalPos.left, this.originalPos.top, this.width, this.height, angle);
//console.log(this);
};
Rectangle.prototype.getCorners = function () {
return [this.leftTopCorner,
this.rightTopCorner,
this.rightBottomCorner,
this.leftBottomCorner];
};
Rectangle.prototype.isCollided = function (rectangle) {
return checkRectangleCollision(this, rectangle);
};
return Rectangle;
}) ();
var rotA = 16;
var widthA = 150;
var heightA = 75;
var htmlRectA = document.getElementById('rectA');
var rotB = 28.9;
var widthB = 50;
var heightB = 130;
var htmlRectB = document.getElementById('rectB');
var msgDiv = document.getElementById('msg');
var rectA = new Rectangle(htmlRectA, widthA, heightA, rotA);
var rectB = new Rectangle(htmlRectB, widthB, heightB, rotB);
window.requestAnimFrame = function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame;
}();
function draw(){
rotA+=1.2;
htmlRectA.setAttribute('style','-ms-transform: rotate('+rotA+'deg);-webkit-transform: rotate('+rotA+'deg);transform: rotate('+rotA+'deg)');
rotB+=5.5;
htmlRectB.setAttribute('style','-ms-transform: rotate('+rotB+'deg);-webkit-transform: rotate('+rotB+'deg);transform: rotate('+rotB+'deg)');
rectA.setCorners(rotA);
rectB.setCorners(rotB);
if(rectA.isCollided(rectB)){
msgDiv.innerHTML = 'Collision detected!';
msgDiv.setAttribute('style','color: #FF0000');
}
else {
msgDiv.innerHTML = 'No Collision!';
msgDiv.setAttribute('style','color: #000000');
}
setTimeout(function(){
window.requestAnimFrame(draw);
},50);
}
window.requestAnimFrame(draw);
#rectA{
background-color: #0000FF;
width:150px;
height:75px;
position:absolute;
top:60px;
left:180px;
-ms-transform: rotate(16deg);
-webkit-transform: rotate(16deg);
transform: rotate(16deg);
}
#rectB{
background-color: #FF0000;
width:50px;
height:130px;
position:absolute;
top:140px;
left:250px;
-ms-transform: rotate(28.9deg);
-webkit-transform: rotate(28.9deg);
transform: rotate(28.9deg);
}
<div id="rectA">A</div>
<div id="rectB">B</div>
<div id="msg"></div>

Drawing a scalloped rectangle

I am trying to draw a scalloped rectangle with mouse like in below image
I am able to draw rectangle with code below
function Rectangle(start, end) {
var w = (end.x - start.x);
var h = (end.y - start.y);
return ["M", start.x, start.y, "L", (start.x + w), start.y, "L", start.x + w, start.y + h, "L", start.x, start.y + h, "L", start.x, start.y].join(' ');
}
var point;
document.addEventListener('mousedown', function(event) {
point = {
x: event.clientX,
y: event.clientY
}
});
document.addEventListener('mousemove', function(event) {
var target = {
x: event.clientX,
y: event.clientY
}
if(point) {
var str = Rectangle(point, target);
document.getElementById('test').setAttribute('d', str);
}
});
document.addEventListener('mouseup', function(event) {
point = null;
});
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0
}
svg {
height:100%;
width: 100%
}
<svg>
<path id="test" style="stroke-width: 4; stroke: RGBA(212, 50, 105, 1.00); fill: none" />
</svg>
But when I try to convert into scalloped rectangle I am seeing different patterns exactly matching Infinite Monkey Theorm
The approach I tried is, draw a rectangular path on a virtual element. Take each point at length multiplied by 15 till total length. Then drawing arcs between those points. It is not working. Also, I want to avoid using getPointAtLength because it's mobile support is not great.
var pathEle = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathEle.setAttribute('d', rectangle(point, target));
window.pathEle = pathEle;
var points = [];
for (var i = 0; i < pathEle.getTotalLength(); i += 15) {
points.push(pathEle.getPointAtLength(i));
}
document.getElementById('test1').setAttribute('d', toSVGPath(points));
Something like this?
I'm using arcs to make the scallops. You may want to tweak how the scallops are calculated to get better corners. But I'll leave that to you.
var scallopSize = 30;
function Rectangle(start, end) {
var minX = Math.min(start.x, end.x);
var minY = Math.min(start.y, end.y);
var w = Math.abs(end.x - start.x);
var h = Math.abs(end.y - start.y);
// Calculate scallop sizes
var numW = Math.round(w / scallopSize);
if (numW === 0) numW = 1;
var numH = Math.round(h / scallopSize);
if (numH === 0) numH = 1;
var stepW = w / numW;
var stepH = h / numH;
// top
var p = minX + stepW/2; // start each size at half a scallop along
var path = ["M", p, minY];
for (var i=1; i < numW; i++) { // numW-1 scallops per side
p += stepW;
path.push('A');
path.push(stepW/2 + 1); // Add 1 to the radius to ensure it's
path.push(stepW/2 + 1); // big enough to span the stepW
path.push("0 0 1");
path.push(p);
path.push(minY);
}
// top right
var p = minY + stepH/2;
path.push('A');
path.push(stepH/2.8); // 2 * sqrt(2)
path.push(stepH/2.8); // corners are a little smaller than the scallops
path.push("0 0 1");
path.push(minX + w);
path.push(p);
// right
for (var i=1; i < numH; i++) {
p += stepH;
path.push('A');
path.push(stepH/2 + 1);
path.push(stepH/2 + 1);
path.push("0 0 1");
path.push(minX + w);
path.push(p);
}
// bottom right
var p = minX + w - stepW/2;
path.push('A');
path.push(stepH/2.8);
path.push(stepH/2.8);
path.push("0 0 1");
path.push(p);
path.push(minY + h);
// bottom
for (var i=1; i < numW; i++) {
p -= stepW;
path.push('A');
path.push(stepW/2 + 1);
path.push(stepW/2 + 1);
path.push("0 0 1");
path.push(p);
path.push(minY + h);
}
// bottom left
var p = minY + h - stepH/2;
path.push('A');
path.push(stepH/2.8);
path.push(stepH/2.8);
path.push("0 0 1");
path.push(minX);
path.push(p);
// left
for (var i=1; i < numH; i++) {
p -= stepH;
path.push('A');
path.push(stepH/2 + 1);
path.push(stepH/2 + 1);
path.push("0 0 1");
path.push(minX);
path.push(p);
}
// top left
path.push('A');
path.push(stepH/2.8);
path.push(stepH/2.8);
path.push("0 0 1");
path.push(minX + stepW/2);
path.push(minY);
path.push('Z');
return path.join(' ');
}
var point;
document.addEventListener('mousedown', function(event) {
point = {
x: event.clientX,
y: event.clientY
}
});
document.addEventListener('mousemove', function(event) {
var target = {
x: event.clientX,
y: event.clientY
}
if(point) {
var str = Rectangle(point, target);
document.getElementById('test').setAttribute('d', str);
}
});
document.addEventListener('mouseup', function(event) {
point = null;
});
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0
}
svg {
height:100%;
width: 100%
}
<svg>
<path id="test" style="stroke-width: 4; stroke: RGBA(212, 50, 105, 1.00); fill: none" />
</svg>

Categories