I found some code for a chart on Codepen (credits: Henry Poydar) that uses snap.svg to create an animated gauge chart.
angular.module('app', []);
angular.module('app')
.controller('metricsCtrl', function($scope) {
$scope.percentage = .8;
var polar_to_cartesian, svg_circle_arc_path, animate_arc;
polar_to_cartesian = function(cx, cy, radius, angle) {
var radians;
radians = (angle - 90) * Math.PI / 180.0;
return [Math.round((cx + (radius * Math.cos(radians))) * 100) / 100, Math.round((cy + (radius * Math.sin(radians))) * 100) / 100];
};
svg_circle_arc_path = function(x, y, radius, start_angle, end_angle) {
var end_xy, start_xy;
start_xy = polar_to_cartesian(x, y, radius, end_angle);
end_xy = polar_to_cartesian(x, y, radius, start_angle);
return "M " + start_xy[0] + " " + start_xy[1] + " A " + radius + " " + radius + " 0 0 0 " + end_xy[0] + " " + end_xy[1];
};
animate_arc = function(ratio, svg, perc) {
var arc;
arc = svg.path('');
return Snap.animate(0, ratio, (function(val) {
var path;
arc.remove();
path = svg_circle_arc_path(500, 500, 450, -90, val * 180.0 - 90);
arc = svg.path(path);
arc.attr({
class: 'data-arc'
});
perc.text(Math.round(val * 100) + '%');
}), Math.round(2000 * ratio), mina.easeinout);
};
$scope.$watch('percentage', function() {
$('.metric').each(function() {
var ratio, svg, perc;
//ratio = $(this).data('ratio');
ratio = $scope.percentage;
svg = Snap($(this).find('svg')[0]);
perc = $(this).find('text.percentage');
animate_arc(ratio, svg, perc);
});
});
});
.metric {
padding: 10%;
}
.metric svg {
max-width: 100%;
}
.metric path {
stroke-width: 75;
stroke: #ecf0f1;
fill: none;
}
.metric path.data-arc {
stroke: #3498db;
}
.metric text {
fill: #3498db;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.4.1/snap.svg-min.js"></script>
<div ng-app="app" ng-controller="metricsCtrl">
<div class="metric">
<svg viewBox="0 0 1000 500">
<path d="M 950 500 A 450 450 0 0 0 50 500"></path>
<text class='percentage' text-anchor="middle" alignment-baseline="middle" x="500" y="300" font-size="140" font-weight="bold">0%
</text>
<text class='title' text-anchor="middle" alignment-baseline="middle" x="500" y="450" font-size="90" font-weight="normal">Empty
</text>
</svg>
</div>
<input ng-model="percentage">
</div>
I would like to be able to dynamically update the charts data and have the SVG render accordingly. I am able to get the chart to show an increase value, but a decrease in value is not working. Here is a demo that reproduces my problem http://codepen.io/EvanWieland/pen/bpxqpV. In the demo, if you increase the value in the input below the chart and then decrease it, you will be able to observe my dilemma. Note that the demo uses Angularjs, this is not a requirement. Thanks in advanced!
This was due to "svg.path(path)" that creates a new arc each time, thus decreasing value draws an arc hidden by the previous ones. The solution is to remove the previous arc at each repaint.
angular.module('app', []);
angular.module('app')
.controller('metricsCtrl', function($scope) {
$scope.percentage = .8;
var polar_to_cartesian, svg_circle_arc_path, animate_arc;
polar_to_cartesian = function(cx, cy, radius, angle) {
var radians;
radians = (angle - 90) * Math.PI / 180.0;
return [Math.round((cx + (radius * Math.cos(radians))) * 100) / 100, Math.round((cy + (radius * Math.sin(radians))) * 100) / 100];
};
svg_circle_arc_path = function(x, y, radius, start_angle, end_angle) {
var end_xy, start_xy;
start_xy = polar_to_cartesian(x, y, radius, end_angle);
end_xy = polar_to_cartesian(x, y, radius, start_angle);
return "M " + start_xy[0] + " " + start_xy[1] + " A " + radius + " " + radius + " 0 0 0 " + end_xy[0] + " " + end_xy[1];
};
animate_arc = function(ratio, svg, perc) {
var arc;
arc = svg.path('');
return Snap.animate(0, ratio, (function(val) {
var path;
arc.remove();
path = svg_circle_arc_path(500, 500, 450, -90, val * 180.0 - 90);
var previousArc = svg.select('.data-arc')
if (previousArc){
previousArc.remove(); // REMOVES PREVIOUS ARC
}
arc = svg.path(path);
arc.attr({
class: 'data-arc'
});
perc.text(Math.round(val * 100) + '%');
}), Math.round(2000 * ratio), mina.easeinout);
};
$scope.$watch('percentage', function() {
$('.metric').each(function() {
var ratio, svg, perc;
//ratio = $(this).data('ratio');
ratio = $scope.percentage;
svg = Snap($(this).find('svg')[0]);
perc = $(this).find('text.percentage');
animate_arc(ratio, svg, perc);
});
});
});
.metric {
padding: 10%;
}
.metric svg {
max-width: 100%;
}
.metric path {
stroke-width: 75;
stroke: #ecf0f1;
fill: none;
}
.metric path.data-arc {
stroke: #3498db;
}
.metric text {
fill: #3498db;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.4.1/snap.svg-min.js"></script>
<div ng-app="app" ng-controller="metricsCtrl">
<div class="metric">
<svg viewBox="0 0 1000 500">
<path d="M 950 500 A 450 450 0 0 0 50 500"></path>
<text class='percentage' text-anchor="middle" alignment-baseline="middle" x="500" y="300" font-size="140" font-weight="bold">0%
</text>
<text class='title' text-anchor="middle" alignment-baseline="middle" x="500" y="450" font-size="90" font-weight="normal">Empty
</text>
</svg>
</div>
<input ng-model="percentage">
</div>
Related
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>
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>
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(' ')
}
This question is an extension of Define a circle / arc animation in SVG and How to calculate the SVG Path for an arc (of a circle).
I have modified the answer of #opsb as follows:
function calculateArcPath(x, y, radius, spread, startAngle, endAngle){
var innerStart = polarToCartesian(x, y, radius, endAngle);
var innerEnd = polarToCartesian(x, y, radius, startAngle);
var outerStart = polarToCartesian(x, y, radius + spread, endAngle);
var outerEnd = polarToCartesian(x, y, radius + spread, startAngle);
var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
var d = [
"M", outerStart.x, outerStart.y,
"A", radius + spread, radius + spread, 0, largeArcFlag, 0, outerEnd.x, outerEnd.y,
"L", innerEnd.x, innerEnd.y,
"A", radius, radius, 0, largeArcFlag, 1, innerStart.x, innerStart.y,
"L", outerStart.x, outerStart.y, "Z"
].join(" ");
return d;
}
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
var startPath = calculateArcPath(250, 250, 50, 30, 0, 30)
var endPath = calculateArcPath(250, 250, 50, 30, 0, 150)
d3.select("path").attr("d", startPath)
d3.select("path").transition().duration(2000).attr("d", endPath)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="500" height="500" style="border:1px gray solid">
<path id="path" fill="blue" stroke="black"></path>
</svg>
However, the path isnt a smooth transition around the circle.
Your animation looks weird because you're animating linearly between two curve shapes. One end point stays fixed while the other moves in a straight line (instead of along an arc).
I think you'll find it much easier to use the stroke-dashoffset trick to animate your curve. Here's a simple example:
function update_arc() {
/* Fetch angle from input field */
angle = document.getElementById("ang").value;
if (angle < 0) angle = 0;
if (angle > 360) angle = 360;
/* Convert to path length using formula r * θ (radians) */
var arclen = Math.PI * 50 * (360-angle) / 180.0;
/* Set stroke-dashoffset attribute to new arc length */
document.getElementById("c").style.strokeDashoffset = arclen;
}
#c {
stroke-dashoffset: 157.08;
stroke-dasharray: 314.16;
-webkit-transition: stroke-dashoffset 0.5s;
transition: stroke-dashoffset 0.5s;
}
<svg width="120" height="120" viewBox="0 0 120 120">
<!-- Circle element of radius 50 units. -->
<!-- (Rotated 90° CCW to put start point is at top.) -->
<circle id="c" cx="60" cy="60"
r="50" fill="none" stroke="blue"
stroke-width="15"
stroke-dashoffset="157.08"
stroke-dasharray="314.16"
transform="rotate(-90 60 60)" />
</svg>
<p>Enter new angle (0–360):<br />
<input type="text" id="ang" width="20" value="180" />
<button onclick="return update_arc()">Set</button></p>
As I move my slider I am drawing an arc. I have made it dynamic. If I change the points of arc then the line drawn will also change. Same thing I want to do with a line. If I move my slider the line should get drawn. Also I want to make it dynamic means If i change the line points the line should also change.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH5EX10: Moving In A Simple Geometric Spiral </title>
<style>
.wrapper {
margin: 0 auto;
width: 1000px;
}
.uppleft {
float: left;
width: 1000px;
position: relative;
margin: 0 0 500px 10px;
}
.dnleft {
float: left;
width: 1000px;
}
.clear {
clear: both;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="uppleft">
<canvas id="canvasOne" width="300" height="300"width="500" height="500" style="position:absolute; left:5px; top:10px; border:1px solid red;">
Your browser does not support HTML5 Canvas.
</canvas>
<canvas id="canvasTwo" width="300" height="300" style="position:absolute; left:250px; top:30px; border:1px solid red;">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
<div class="clear"></div>
<div class="dnleft">
<input id="slide1" type="range" min="0" max="100" step="1" value="0" onchange="counterSliderNew('slide1', '100');"/>
</div>
</div>
</body>
<script type="text/javascript">
drawSlopeCurve2('canvasTwo', 16, 170, 200, 80)
function counterSliderNew(sID, maxValue) {
var slideVal = document.getElementById(sID).value;
//alert(slideVal);
if (maxValue == 100) {
slideVal = slideVal / 100;
}
if (slideVal == 0) {
} else if (slideVal > 0 && slideVal <= 34) {
erase('canvasOne');
drawBezier2('canvasOne', new Array({
x : 18.8,
y : 75
}, {
x : 28,
y : 160
}, {
x : 228,
y : 165
}), slideVal);
} else if (slideVal > 34 && slideVal <= 67) {
//alert(slideVal);
erase('canvasOne');
drawBezier2('canvasOne', new Array({
x : 18.8,
y : 75
}, {
x : 28,
y : 160
}, {
x : 228,
y : 165
}), slideVal);
staticGraph5('canvasTwo');
} else if (slideVal > 67 && slideVal <= 100) {
erase('canvasOne');
drawBezier2('canvasOne', new Array({
x : 18.8,
y : 75
}, {
x : 28,
y : 160
}, {
x : 228,
y : 165
}), slideVal);
}
}
function drawBezier2(canId, points, slideVal) {
var canvas = document.getElementById(canId);
var context = canvas.getContext("2d");
// Draw guides
context.lineWidth = 2;
context.strokeStyle = "rgb(113, 113, 213)";
context.beginPath();
// Label end points
context.fillStyle = "rgb(0, 0, 0)";
// Draw spline segemnts
context.moveTo(points[0].x, points[0].y);
for (var t = 0; t <= slideVal; t += 0.1) {
context.lineTo(Math.pow(1 - t, 2) * points[0].x + 2 * (1 - t) * t * points[1].x + Math.pow(t, 2) * points[2].x, Math.pow(1 - t, 2) * points[0].y + 2 * (1 - t) * t * points[1].y + Math.pow(t, 2) * points[2].y);
}
// Stroke path
context.stroke();
}
function erase(canvasId) {
var canvas = document.getElementById(canvasId);
var context = canvas.getContext("2d");
context.beginPath();
context.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = canvas.width;
}
function drawSlopeCurve2(canId, mvx, mvy, lnx, lny) {
var canvas = document.getElementById(canId);
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(mvx, mvy);
context.lineTo(lnx, lny);
context.lineWidth = 0.6;
context.stroke();
}
</script>
</html>
This will work only in chrome. I want to use drawSlopeCurve2('canvasTwo', 16, 170, 200, 80); this function to draw a line on movement of slider. I am looking for some formula as I have used for moving an arc on slider movement.
You can use this function to get an array of points that interpolate a line between any 2 points:
function linePoints(x1, y1, x2, y2, frames) {
var dx = x2 - x1;
var dy = y2 - y1;
var length = Math.sqrt(dx * dx + dy * dy);
var incrementX = dx / frames;
var incrementY = dy / frames;
var a = new Array();
a.push({ x: x1, y: y1 });
for (var frame = 0; frame < frames - 1; frame++) {
a.push({
x: x1 + (incrementX * frame),
y: y1 + (incrementY * frame)
});
}
a.push({ x: x2, y: y2 });
return (a);
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/qhzJv/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH5EX10: Moving In A Simple Geometric Spiral </title>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<style>
.wrapper {
width: 700px;
height:350px;
border:2px solid green;
padding:15px;
}
.uppleft{
display: inline;
margin-left: 30px;
}
canvas{
border:1px solid red;
}
#sliderwrapper{
display: inline-block;
position:relative;
width:37px; height:300px;
border:1px solid blue;
}
#amount{
position:absolute;
left:5px; top:5px;
margin-bottom:15px;
width:23px;
border:0; color:#f6931f;
font-weight:bold;
}
#slider-vertical{
position:absolute;
left:10px; top:40px;
width:15px; height:225px;
border:0px; color:#f6931f;
font-weight:bold;
}
</style>
</head>
<body>
<div class="wrapper">
<div id="sliderwrapper">
<input type="text" id="amount" />
<div id="slider-vertical"></div>
</div>
<div class="uppleft">
<canvas id="canvasOne" width="300" height="300">
Your browser does not support HTML5 Canvas.
</canvas>
<canvas id="canvasTwo" width="300" height="300">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
</div>
</body>
<script type="text/javascript">
var startingValue=20;
// handles user moving the slider
$( "#slider-vertical" ).slider({
orientation: "vertical",
range: "min",
min: 0,
max: 100,
value: startingValue,
slide: function( event, ui ) {
$( "#amount" ).val( ui.value );
counterSliderNew('slide1', '100', ui.value);
}
});
// get an array of 100 points between start and end of line
var points=linePoints(16, 170, 200, 80,100);
// draw the initial point based on the beginning slider value
counterSliderNew('slide1', '100', startingValue);
function counterSliderNew(sID, maxValue,theSliderValue) {
var slideVal = theSliderValue; // document.getElementById(sID).value;
// get the slider value and get the point at points[slideVal]
var point=points[slideVal];
erase('canvasTwo');
drawSlopeCurve2('canvasTwo',16,170,point.x,point.y);
if (maxValue == 100) {
slideVal = slideVal / 100;
}
if (slideVal == 0) {
} else if (slideVal > 0 && slideVal <= 34) {
erase('canvasOne');
drawBezier2('canvasOne', new Array({
x : 18.8,
y : 75
}, {
x : 28,
y : 160
}, {
x : 228,
y : 165
}), slideVal);
} else if (slideVal > 34 && slideVal <= 67) {
//alert(slideVal);
erase('canvasOne');
drawBezier2('canvasOne', new Array({
x : 18.8,
y : 75
}, {
x : 28,
y : 160
}, {
x : 228,
y : 165
}), slideVal);
staticGraph5('canvasTwo');
} else if (slideVal > 67 && slideVal <= 100) {
erase('canvasOne');
drawBezier2('canvasOne', new Array({
x : 18.8,
y : 75
}, {
x : 28,
y : 160
}, {
x : 228,
y : 165
}), slideVal);
}
}
function drawBezier2(canId, points, slideVal) {
var canvas = document.getElementById(canId);
var context = canvas.getContext("2d");
// Draw guides
context.lineWidth = 2;
context.strokeStyle = "rgb(113, 113, 213)";
context.beginPath();
// Label end points
context.fillStyle = "rgb(0, 0, 0)";
// Draw spline segemnts
context.moveTo(points[0].x, points[0].y);
for (var t = 0; t <= slideVal; t += 0.1) {
context.lineTo(Math.pow(1 - t, 2) * points[0].x + 2 * (1 - t) * t * points[1].x + Math.pow(t, 2) * points[2].x, Math.pow(1 - t, 2) * points[0].y + 2 * (1 - t) * t * points[1].y + Math.pow(t, 2) * points[2].y);
}
// Stroke path
context.stroke();
}
function erase(canvasId) {
var canvas = document.getElementById(canvasId);
var context = canvas.getContext("2d");
context.beginPath();
context.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = canvas.width;
}
function drawSlopeCurve2(canId, mvx, mvy, lnx, lny) {
var canvas = document.getElementById(canId);
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(mvx, mvy);
context.lineTo(lnx, lny);
context.lineWidth = 0.6;
context.stroke();
}
function linePoints(x1, y1, x2, y2, frames) {
var dx = x2 - x1;
var dy = y2 - y1;
var length = Math.sqrt(dx * dx + dy * dy);
var incrementX = dx / frames;
var incrementY = dy / frames;
var a = new Array();
a.push({ x: x1, y: y1 });
for (var frame = 0; frame < frames - 1; frame++) {
a.push({
x: x1 + (incrementX * frame),
y: y1 + (incrementY * frame)
});
}
a.push({ x: x2, y: y2 });
return (a);
}
</script>
</html>