create svg hexagon points with only only a length - javascript

I am trying to create a hexagon using an svg polygon.
I want to create the x and why coordinates but my code is not working.
I thought I could use the trig functions by transforming each point by 60 degrees.
It is clearly not working.
const radius = 25;
const points = [0, 1, 2, 3, 4, 5, 6].map((n) => {
const current = n * 60;
return [radius * Math.cos(current), -radius * Math.sin(current)];
}).map((p) => p.join(','))
.join(' ');
document.querySelector('polygon')
.setAttribute("points", "100,0 50,0 100,100");
<svg width="200px" height="200px" viewBox="0 0 200 200">
<polygon points="" style="fill: #ccffcc; stroke: red;stroke-width: 3;"
/>
</svg>

According to this article, it can be converted to javascript like:
const radius = 50;
const height = 200;
const width = 200;
const points = [0, 1, 2, 3, 4, 5, 6].map((n, i) => {
var angle_deg = 60 * i - 30;
var angle_rad = Math.PI / 180 * angle_deg;
return [width/2 + radius * Math.cos(angle_rad), height/2 + radius * Math.sin(angle_rad)];
}).map((p) => p.join(','))
.join(' ');
document.querySelector('polygon')
.setAttribute("points", points);
<svg width="200px" height="200px" viewBox="0 0 200 200">
<polygon points="" style="fill: #ccffcc; stroke: red;stroke-width: 3;"
/>
</svg>

You have one too many indexes in the example above and your actually adding commas in your first join when you don't need to be. Here is a cleaned-up version.
const generateHexPoints = (radius, height, width) => {
const hexPoints = new Array(6)
for (let i = 0; i < hexPoints.length; i++) {
const angleDeg = 60 * i - 30
const angleRad = (Math.PI / 180) * angleDeg;
hexPoints[i] = [
width/2 + radius * Math.cos(angleRad),
height/2 + radius * Math.sin(angleRad)
];
}
return hexPoints.map((p) => p.join(' ')).join(' ')
}

Related

JS SVG: get path from SVG polgon

I have the following SVG element which was created using JS: https://akzhy.com/blog/create-animated-donut-chart-using-svg-and-javascript
<div class="doughnut">
<svg width="100%" height="100%" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="30" stroke="#80e080" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="141.372" transform='rotate(-90 50 50)'/>
<circle cx="50" cy="50" r="30" stroke="#4fc3f7" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="103.6728" transform='rotate(0 50 50)'/>
<circle cx="50" cy="50" r="30" stroke="#9575cd" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="169.6464" transform='rotate(162 50 50)'/>
<circle cx="50" cy="50" r="30" stroke="#f06292" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="150.7968" transform='rotate(198 50 50)'/>
</svg>
</div>
Is it possible to to get a path from the svg node?
Conversion via graphic app
Open you svg in an application like Illustrator/inkscape etc.
You could use path operations like "stroke-to-path" to convert stroke based chart segments (the visual colored segments are just dashed strokes applied to a full circle).
Use a pie/donut generator script returning solid paths
Based on this answer by #ray hatfield Pie chart using circle element you can calculate d properties based on the arc command.
Example json based pie chart generator
let pies = document.querySelectorAll('.pie-generate');
generatePies(pies);
function generatePies(pies) {
if (pies.length) {
pies.forEach(function(pie, i) {
let data = pie.getAttribute('data-pie');
if (data) {
data = JSON.parse(data);
w = data['width'];
h = data['height'];
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
svg.setAttribute('width', w);
svg.setAttribute('height', h);
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
pie.appendChild(svg);
addSegments(svg, data);
}
})
}
}
function addSegments(svg, data) {
let segments = data["segments"];
let strokeWidth = data["strokeWidth"];
let centerX = data["centerX"];
let centerY = data["centerY"];
let radius = data["radius"];
let startingAngle = (data["startingAngle"] || data["startingAngle"] == 0) ? data["startingAngle"] : -90;
let gap = data["gap"];
let decimals = data["decimals"];
let offset = 0;
let output = "";
// calculate auto percentages
let total = 0;
let calc = data["calc"] ? true : false;
if (calc) {
segments.forEach(function(segment, i) {
total += segment[0];
});
}
// prevent too large gaps
let circumference = Math.PI * radius * 2;
let circumferencePerc = (circumference / 100);
let gapPercentOuter = 100 / circumference * gap;
if (gapPercentOuter > circumferencePerc) {
gap = gap / (gapPercentOuter / circumferencePerc)
}
segments.forEach(function(segment, i) {
let percent = segment[0];
// calc percentages
let percentCalc = percent.toString().indexOf('/') != -1 ? segment[0].split('/') : [];
percent = percentCalc.length ? percentCalc[0] / percentCalc[1] * 100 : +percent
// calculate auto percentages to get 100% in total
if (total) {
percent = 100 / total * percent;
}
let percentRound = percent.toFixed(decimals);
// auto fill color
let segOptions = segment[1] ? segment[1] : '';
let fill = segOptions ? 'fill="' + segOptions['color'] + '"' : "";
if (!fill) {
let hueCut = 0;
let hueShift = 0;
let hue = Math.abs((360 - hueCut) / 100 * (offset + percent)) + hueShift;
let autoColor = hslToHex(hue.toFixed(0) * 1, 60, 50);
fill = 'fill="' + autoColor + '"';
}
let className = segOptions['class'] ? segOptions['class'] : "";
let classPercent = percentRound.toString().replaceAll('.', '_');
let id = segOptions['id'] ? 'id="' + segOptions['id'] + '" ' : '';
let d = getArcD(centerX, centerY, strokeWidth, offset, percent, radius, gap, decimals, startingAngle);
output +=
`\n<path d="${d}" ${fill} class="segment segment-${classPercent} segment-${(i+1)} ${className}" ${id} data-percent="${percentRound}"/>`;
offset += percent;
});
svg.innerHTML = output;
}
function getArcD(centerX, centerY, strokeWidth, percentStart, percent, radiusOuter, gap, decimals = 3, startingAngle = -90) {
let radiusInner = radiusOuter - strokeWidth;
let circumference = Math.PI * radiusOuter * 2;
let isPieChart = false;
// if pie chart – stroke equals radius
if (strokeWidth + gap >= radiusOuter) {
isPieChart = true;
}
let circumferenceInner = Math.PI * radiusInner * 2;
let gapPercentOuter = ((100 / circumference) * gap) / 2;
let gapPercentInner = ((100 / circumferenceInner) * gap) / 2;
//add offset from previous segments
percentStart = percentStart;
let percentEnd = percent + percentStart;
// outer coordinates
let [x1, y1] = getPosOnCircle(centerX, centerY, (percentStart + gapPercentOuter), radiusOuter, decimals, startingAngle);
let [x2, y2] = getPosOnCircle(centerX, centerY, percentEnd - gapPercentOuter, radiusOuter, decimals, startingAngle);
// switch arc output between long or short arc segment according to percentage
let longArc = percent >= 50 ? 1 : 0;
let rotation = 0;
let clockwise = 1;
let counterclockwise = 0;
let d = '';
// if donut chart
if (!isPieChart) {
//inner coordinates
let [x3, y3] = getPosOnCircle(centerX, centerY, percentEnd - gapPercentInner, radiusInner, decimals, startingAngle);
let [x4, y4] = getPosOnCircle(centerX, centerY, percentStart + gapPercentInner, radiusInner, decimals, startingAngle);
d = [
"M", x1, y1,
"A", radiusOuter, radiusOuter, rotation, longArc, clockwise, x2, y2,
"L", x3, y3,
"A", radiusInner, radiusInner, rotation, longArc, counterclockwise, x4, y4,
"z"
];
}
// if pie chart – stroke equals radius: drop inner radius arc
else {
// find opposite coordinates
let [x1o, y1o] = getPosOnCircle(centerX, centerY, (percentStart - gapPercentOuter) - 50, radiusOuter, decimals, startingAngle);
let [x2o, y2o] = getPosOnCircle(centerX, centerY, (percentEnd + gapPercentOuter) - 50, radiusOuter, decimals, startingAngle);
let extrapolatedIntersection = getLinesIntersection(
[x1, y1, x1o, y1o], [x2, y2, x2o, y2o],
decimals);
d = [
"M", x1, y1,
"A", radiusOuter, radiusOuter, rotation, longArc, clockwise, x2, y2,
"L", extrapolatedIntersection.join(" "),
"z"
];
}
return d.join(" ");
}
// helper: get x/y coordinates according to angle percentage
function getPosOnCircle(centerX, centerY, percent, radius, decimals = 3, angleOffset = -90) {
let angle = 360 / (100 / percent) + angleOffset;
let x = +(centerX + Math.cos((angle * Math.PI) / 180) * radius).toFixed(
decimals
);
let y = +(centerY + Math.sin((angle * Math.PI) / 180) * radius).toFixed(
decimals
);
return [x, y];
}
// helper: get intersection coordinates
function getLinesIntersection(l1, l2, decimals = 3) {
let intersection = [];
let c2x = l2[0] - l2[2];
let c3x = l1[0] - l1[2];
let c2y = l2[1] - l2[3];
let c3y = l1[1] - l1[3];
// down part of intersection point formula
let d = c3x * c2y - c3y * c2x;
if (d != 0) {
// upper part of intersection point formula
let u1 = l1[0] * l1[3] - l1[1] * l1[2];
let u4 = l2[0] * l2[3] - l2[1] * l2[2];
// intersection point formula
let px = +((u1 * c2x - c3x * u4) / d).toFixed(decimals);
let py = +((u1 * c2y - c3y * u4) / d).toFixed(decimals);
intersection = [px, py];
}
return intersection;
}
function hslToHex(h, s, l) {
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color).toString(16).padStart(2, '0'); // convert to Hex and prefix "0" if needed
};
return `#${f(0)}${f(8)}${f(4)}`;
}
<div class="pie-generate" data-pie='{
"width": 100,
"height": 100,
"radius": 50,
"centerX": 50,
"centerY": 50,
"strokeWidth": 20,
"gap": 0,
"decimals": 3,
"segments": [
["25", {"color":"#80e080", "id":"seg01", "class":"segCustom"}],
["45", {"color":"#4fc3f7", "id":"seg02", "class":"segCustom"}],
["10", {"color":"#9575cd", "id":"seg03", "class":"segCustom"}],
["20", {"color":"#f06292", "id":"seg04", "class":"segCustom"}]
]
}'>
</div>
You can tweak different segment percentages by changing the JSOn data-attribute.
<div class="pie-generate" data-pie='{
"width": 100,
"height": 100,
"radius": 50,
"centerX": 50,
"centerY": 50,
"strokeWidth": 20,
"gap": 0,
"decimals": 3,
"segments": [
["25", {"color":"#80e080", "id":"seg01", "class":"segCustom"}],
["45", {"color":"#4fc3f7", "id":"seg02", "class":"segCustom"}],
["10", {"color":"#9575cd", "id":"seg03", "class":"segCustom"}],
["20", {"color":"#f06292", "id":"seg04", "class":"segCustom"}]
]
}'>
</div>
Segment output:
<path d="M 50 0 A 50 50 0 0 1 100 50 L 80 50 A 30 30 0 0 0 50 20 z" fill="#80e080" />

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>

Connecting quadratic bézier circle in SVG

I am creating a random SVG "Blob" generator, but can't figure out how to connect the last bézier to the "M" point correctly. In the example you can see a little spike there.
function generate() {
const points = [
{ x: 55.380049480163834, y: 8.141661255952418 },
{ x: 61.89338428790346, y: 59.21935310168805 },
{ x: 6.637386502817552, y: 65.10477483405401 },
{ x: 15.309460889587692, y: 11.231848017862793 }
]
let d = `M ${points[0].x / 2} ${points[0].y}`
d += `Q ${points[0].x} ${points[0].y} ${(points[0].x + points[1].x) * 0.5} ${(points[0].y + points[1].y) * 0.5}`
d += `Q ${points[1].x} ${points[1].y} ${(points[1].x + points[2].x) * 0.5} ${(points[1].y + points[2].y) * 0.5}`
d += `Q ${points[2].x} ${points[2].y} ${(points[2].x + points[3].x) * 0.5} ${(points[2].y + points[3].y) * 0.5}`
d += `Q ${points[3].x} ${points[3].y} ${(points[3].x + points[0].x) * 0.5} ${(points[3].y + points[0].y) * 0.5} Z`
return d
}
document.getElementById('blob').setAttribute('d', generate())
<svg>
<path viewBox="0 0 70 70" id="blob"></path>
</svg>
This is how I would do it: first you need to find the first midpoint (between the last and the first point ) and move to it. next you calculate the midpoint between every 2 points in the points array. Finally you draw the curve through the last point, back to the first midpoint.
const points = [
{ x: 55.380049480163834, y: 8.141661255952418 },
{ x: 61.89338428790346, y: 59.21935310168805 },
{ x: 6.637386502817552, y: 65.10477483405401 },
{ x: 15.309460889587692, y: 11.231848017862793 }
]
function drawCurve(points) {
//find the first midpoint and move to it
var p = {};
p.x = (points[points.length - 1].x + points[0].x) / 2;
p.y = (points[points.length - 1].y + points[0].y) / 2;
let d = `M${p.x}, ${p.y}`;
//curve through the rest, stopping at each midpoint
for (var i = 0; i < points.length - 1; i++) {
var mp = {}
mp.x = (points[i].x + points[i + 1].x) / 2;
mp.y = (points[i].y + points[i + 1].y) / 2;
d += `Q${points[i].x},${points[i].y},${mp.x},${mp.y}`
}
//curve through the last point, back to the first midpoint
d+= `Q${points[points.length - 1].x},${points[points.length - 1].y},${p.x},${p.y}`
blob.setAttributeNS(null,"d",d)
}
drawCurve(points);
svg{border:1px solid; fill:none; stroke:black; width:300px;}
<svg viewBox="0 0 70 70" >
<path id="blob"></path>
</svg>

Recreating the dynamic donut chart with SVG and Vue

I need a dynamic donut chart with SVG and Vue.
It has to be exactly the same SVG as on this page:
https://www.chipotle.co.uk/nutrition-calculator (to see the chart diagram in motion, first you must choose a dish and then click on some ingredients)
Maybe it was bad direction but that is what I was able find and modify to the task
(https://codepen.io/fifuruho/pen/zYOBWLx)
All code in codeopen.io
I'm really bad at complex svg's.
I need any solutions or tips you can offer.
(but better in code examples)
I would do this in a different way. Instead of using strokes I would use paths. I would make the paths touching one with another and next I would apply the same path (stroke:#000; stroke-width:3; fill: #ffffff) as a mask. You can change the stroke width for a different gap.
This would be the resulting svg:
svg{border:1px solid}
mask use{stroke:#000; stroke-width:3; fill: #ffffff}
<svg width="320" viewBox="-80 -80 160 160" class="donut-chart">
<defs id="theDefs">
<path d="M5.475206276408082,-34.56909192082982 L10.16824022761501,-64.19974213868396 A65,65 0 0 1 10.16824022761501,64.19974213868396 L5.475206276408082,34.56909192082982 A35,35 0 0 0 5.475206276408082,-34.56909192082982" id="elmt45" style="mask: url(#mask45)"></path>
<mask id="mask45">
<use xlink:href="#elmt45"></use>
</mask>
<path d="M18.75393782426488,-29.55147739257053 L34.828741673634774,-54.88131515763098 A65,65 0 0 1 34.828741673634774,54.88131515763098 L18.75393782426488,29.55147739257053 A35,35 0 0 0 18.75393782426488,-29.55147739257053" id="elmt32" style="mask: url(#mask32)"></path>
<mask id="mask32">
<use xlink:href="#elmt32"></use>
</mask>
<path d="M26.253887437066084,-23.145915286327813 L48.75721952597986,-42.98527124603737 A65,65 0 0 1 48.75721952597986,42.98527124603737 L26.253887437066084,23.145915286327813 A35,35 0 0 0 26.253887437066084,-23.145915286327813" id="elmt23" style="mask: url(#mask23)"></path>
<mask id="mask23">
<use xlink:href="#elmt23"></use>
</mask>
</defs>
<g id="theG">
<use xlink:href="#elmt45" fill="rgb(182, 130, 7)" transform="rotate(-9)"></use><use xlink:href="#elmt32" fill="rgb(120, 98, 89)" transform="rotate(129.6)"></use>
<use xlink:href="#elmt23" fill="rgb(195, 180, 166)" transform="rotate(228.6)"></use>
</g>
</svg>
As for the Javascript to produce this, I'm using plain javascript. I hope you'll be able to translate it to Vue.
const SVG_NS = "http://www.w3.org/2000/svg";
const SVG_XLINK = "http://www.w3.org/1999/xlink";
let colors = ["rgb(182, 130, 7)", "rgb(120, 98, 89)", "rgb(195, 180, 166)"];
class Sector {
constructor(color, prev, a) {
this.color = color;
this.R = 65; //external radius
this.r = 35; //inner radius
this.a = a * 360 / 100; //360degs = 100%
this.A = this.a * Math.PI / 180; // angle in radians
this.prev = prev;
this.color = color;
this.elmt = document.createElementNS(SVG_NS, "path");
document.querySelector("#theDefs").appendChild(this.elmt);
this.p1 = {};
this.p2 = {};
this.p3 = {};
this.p4 = {};
this.p1.x = this.r * Math.cos(-this.A / 2);
this.p1.y = this.r * Math.sin(-this.A / 2);
this.p2.x = this.R * Math.cos(-this.A / 2);
this.p2.y = this.R * Math.sin(-this.A / 2);
this.p3.x = this.R * Math.cos(this.A / 2);
this.p3.y = this.R * Math.sin(this.A / 2);
this.p4.x = this.r * Math.cos(this.A / 2);
this.p4.y = this.r * Math.sin(this.A / 2);
this.d = `M${this.p1.x},${this.p1.y} L${this.p2.x},${this.p2.y} A${
this.R
},${this.R} 0 0 1 ${this.p3.x},${this.p3.y} L${this.p4.x},${this.p4.y} A${
this.r
},${this.r} 0 0 0 ${this.p1.x},${this.p1.y}`;
this.elmt.setAttributeNS(null, "d", this.d);
this.elmt.setAttribute("id", `elmt${a}`);
this.mask = document.createElementNS(SVG_NS, "mask");
this.use = document.createElementNS(SVG_NS, "use");
this.use.setAttributeNS(SVG_XLINK, "xlink:href", `#elmt${a}`);
this.mask.appendChild(this.use);
this.mask.setAttribute("id", `mask${a}`);
document.querySelector("#theDefs").appendChild(this.mask);
this.use1 = document.createElementNS(SVG_NS, "use");
this.use1.setAttributeNS(SVG_XLINK, "xlink:href", `#elmt${a}`);
this.use1.setAttributeNS(null, "fill", this.color);
theG.appendChild(this.use1);
this.elmt.setAttribute("style", `mask: url(#mask${a})`);
this.use1.setAttributeNS(
null,
"transform",
`rotate(${-90 + this.a / 2 + this.prev})`
);
}
}
let s1 = new Sector(colors[0], 0, 45);
let s2 = new Sector(colors[1], s1.a, 32);
let s3 = new Sector(colors[2], s1.a + s2.a, 23);
svg{border:1px solid}
mask use{stroke:#000; stroke-width:3; fill: #ffffff}
<svg height="250" width="320" viewBox="-80 -80 160 160" class="donut-chart">
<defs id="theDefs"></defs>
<g id="theG">
</g>
</svg>

Displaying and formatting nested SVGs

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>

Categories