Round Up SVG Path - svg

I am new to drawing vectors using svg and I am trying to create a custom tab bar for my app's navigations. I want it to look something like this:
I was able to create something similar:
And the svg code looks like this:
const width = SCREEN_WIDTH;
const firstStop = width / 2 - 30;
const secondStop = width / 2 + 28;
<Path
d={`
M 0,0
H ${firstStop - 30}
Q ${firstStop},0
${firstStop},0
${firstStop},35
${firstStop + 30},35
${secondStop},35
${secondStop},0
${secondStop},0
${secondStop + 30},0
H ${width}
V 70
H 0
Z`}
fill={colors.accent}
fillRule='nonzero'
/>
But, as you can see, I created it with sharp edges. I wanted to achieve the smoother edge for the "cutout". How can I create such curve in this case? I think i don't quite grasp the Q path in this case.

Related

How to know the m or M point of path from all of points in svg?(use raphael.js)

I have an svg picture like this, with M and m in the path. I use raphael.js to calculate. I can get all the points by calling Raphael.getTotalLength(path), but how can I know from which point is the path behind m.
I want to know all the absolute coordinate points of the inner border of the svg, so that I can get a data format similar to the following [[points of the outer border], [points of the inner border]].
This is how I achieved it. I can get all the points, but I can’t distinguish which are the points of the inner frame and which are the points of the outer frame.
import Raphael from 'raphael';
function getPoints(path) {
let points = [];
for (let i = 0; i < Raphael.getTotalLength(path); i += step_point) {
const point = Raphael.getPointAtLength(path, i);
points.push(point);
}
}
This is the svg content:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
<path
d="M72.8525 8.0439c-.405 0-.816.022-1.222.064-4.973.523-8.939 4.112-10.048 8.833-.73-.028-1.453-.043-2.162-.043-5.453 0-10.307.814-14.464 2.423-1.988-3.703-5.849-6.071-10.173-6.071-.973 0-1.949.124-2.899.37-2.994.773-5.508 2.667-7.078 5.331-1.571 2.664-2.01 5.781-1.236 8.775.772 2.986 2.693 5.506 5.301 7.052-1.056 2.419-1.535 4.533-1.814 6.02-.623 3.314-2.519 13.398 5.355 20.728 3.209 2.988 6.672 4.84 10.937 5.8-3.558 4.888-7.226 11.138-8.02 16.945-.349 2.543-.027 4.749.956 6.576l-.149.355c-.034.081-.062.165-.085.25-.315 1.166-.025 2.398.778 3.295.672.754 1.639 1.187 2.649 1.187.044 0 .088-.001.131-.002.27.406.626.758 1.053 1.029.518.33 1.109.519 1.719.55l-1.044 4.167c-.571 2.283.813 4.636 3.086 5.245l10.106 2.708c.372.1.753.15 1.132.15 1.299 0 2.521-.582 3.351-1.595.825-1.008 1.151-2.32.894-3.6-.013-.064-.007-.13.016-.189l1.1-2.829 3.729 6.22c.78 1.3 2.206 2.109 3.723 2.109.759 0 1.509-.202 2.168-.582l9.061-5.232c1.308-.756 2.119-2.108 2.167-3.619.03-.959-.249-1.873-.779-2.627.6-.052 1.175-.255 1.679-.595.42-.283.766-.644 1.024-1.058h.035c1.047 0 2.037-.459 2.713-1.259.778-.92 1.035-2.16.687-3.318-.025-.084-.056-.166-.092-.246l-.158-.35c.933-1.854 1.194-4.068.777-6.6-1.181-7.17-6.763-14.845-10.84-19.646 1.556-.529 3.061-1.122 4.547-1.793 6.708-3.027 9.062-8.913 9.395-11.913.346-3.113-.969-9.08-2.01-12.015-1.056-2.977-3.244-8.332-6.599-12.674 1.647-2.29 2.406-5.105 2.106-7.957-.621-5.911-5.566-10.369-11.503-10.369m0 2c4.84 0 8.997 3.657 9.514 8.578.312 2.97-.769 5.764-2.716 7.735 4.039 4.59 6.48 11.006 7.313 13.355 1.049 2.957 2.192 8.566 1.908 11.126-.285 2.56-2.435 7.696-8.23 10.311-2.229 1.006-4.658 1.897-7.363 2.639.216.171.429.348.617.556 3.231 3.599 10.589 12.513 11.888 20.392.453 2.753-.065 4.727-1.052 6.153l.583 1.294c.149.498.04 1.049-.299 1.451-.296.349-.728.55-1.186.55-.367 0-.722-.13-1.002-.367-.127-.107-.234-.231-.32-.372l-.182-.406c-.053.03-.108.056-.161.085l.17 1.142c.01.521-.245 1.023-.682 1.318-.258.174-.558.266-.867.266-.518 0-.999-.256-1.288-.685-.093-.138-.163-.287-.208-.447l-.078-.525s0 .001-.001.001c-.092.021-.184.022-.277.035-.125.259-.314.488-.566.645-.247.154-.531.235-.82.235-.22 0-.427-.055-.621-.14l1.058 2.404c.18.409.472.758.845 1.006 1.444.961 1.377 3.104-.126 3.973l-9.061 5.23c-.368.213-.77.315-1.168.315-.795 0-1.57-.407-2.008-1.137l-4.403-7.347c-.752-.015-1.524-.056-2.306-.11l-1.698 4.368c-.163.417-.202.87-.114 1.309.3 1.49-.864 2.801-2.284 2.801-.201 0-.407-.026-.614-.082l-10.106-2.708c-1.234-.33-1.974-1.589-1.664-2.827l1.907-7.612c-.062-.027-.127-.044-.185-.077-.256-.151-.451-.374-.584-.631-.092-.01-.185-.009-.278-.027 0 0-.001 0-.001-.001l-.065.532c-.04.159-.105.309-.193.448-.287.45-.777.72-1.312.72-.294 0-.582-.084-.832-.243-.444-.283-.713-.777-.717-1.298l.139-1.147c-.054-.027-.11-.052-.163-.081l-.174.415c-.082.142-.184.268-.307.377-.285.254-.652.395-1.034.395-.441 0-.864-.19-1.158-.519-.35-.392-.474-.939-.339-1.441l.548-1.311c-1.026-1.397-1.598-3.356-1.219-6.121.917-6.699 6.151-14.247 9.637-18.644-4.885-.547-9.142-2.083-13.173-5.836-6.746-6.28-5.521-14.805-4.752-18.894.384-2.041 1.039-4.558 2.526-7.33-2.881-1.035-5.218-3.424-6.041-6.612-1.324-5.122 1.756-10.347 6.877-11.67.802-.207 1.607-.306 2.399-.306 4.097 0 7.844 2.654 9.119 6.698 5.236-2.473 11.057-3.05 15.518-3.05 1.264 0 2.419.047 3.42.109 0 0 .169.006.449.024.281-4.587 3.828-8.437 8.55-8.934.34-.035.678-.053 1.013-.053"
fill="#F00" stroke="#000" />
</svg>
I really look forward to your answers, thank you!
As I mentioned elsewhere. You can split on M. If for some reason this doesn't make sense (as you mentioned multiple 'm's) as you say "behind m", then you need to amend your question to be more precise about where the split would be, if this is part of a more generic issue.
So we can split the string on "m"
var match = new RegExp("(^[^m]*)(.*)", "").exec(path);
And get the last point of the first path part...
var point = Raphael.getPointAtLength(match[1], p.getTotalLength(match[1]));
Then add the final point to the first path...
var p2 = r.path("M" + point.x + "," + point.y + match[2]).attr('stroke','blue')
And I've amended getPoints()...
function getPoints(path) {
let points = [];
let step_point = 10;
for (let i = 0; i < path.getTotalLength(); i += step_point) {
const point = path.getPointAtLength(i);
points.push(point);
}
return points;
}
And get the sets of points...
console.log(getPoints(p1));
console.log(getPoints(p2));
jsfiddle showing different colours for outer/inner and it dumps in the console the two sets of points.

SVG or Canvas bevel & emboss to find center-line of text

The photoshop bevel & emboss effect makes it easy to find the central 'ridge' which coincides with the center-line of text characters. This is done by increasing the appropriate effect settings to max-out the bevel, thereby creating such a ridge.
This Photoshop example was processed to further accentuate the center ridge
Is it possible to achieve the same effect with an SVG filter or Canvas technique in the browser?
Once this effect is in place, I could obtain the coordinates of the center line which I want.
Alternatively, is there an existing algorithm to get this center line via mathematical means from a raster image or vector shape?
SVG filters are a powerful feature that can be like photoshop in the browser. You can achieve the desired result by chaining a handful of filter primitives together.
<filter id="filterData">
<feGaussianBlur stdDeviation="5" />
<feDiffuseLighting surfaceScale="500">
<feDistantLight azimuth="90" elevation="90" />
</feDiffuseLighting>
<feComposite result="composite" operator="in" in2="SourceGraphic" />
</filter>
The first primitive blurs the text. Then a lighting primitive uses the result of the blurred primitive as a bump map to give the text depth. You will have to play with the surfaceScale attribute depending to the thickness of the text. The composite primitive will cut the final result to the area of the unfiltered text, the 'SourceGraphic'.
[codepen example] https://codepen.io/lahaymd/pen/EdNXam
Somehow this one tickled my fancy, although I am not sure this is an efficient way to get a result.
What is the center line? I define it as the set of all points inside the contour that fullfill the following condition: There must be at least one straight line going through the point where the distance to the nearest contour line is a local maximum along the line just at that point. In practice, testing a horizontal and a vertical line is enough.
I tried to implement that using two functions from the SVGGeometryElement interface: .getPointAtLength() and .isPointInFill(). The second one has so far only been implemented in Chrome, so that is the only browser this will work with.
The <text> element does not implement the SVGGeometryElement interface, so it must be converted to a <path>. That is something that cannot be done in a browser, you'll need an appropriate grafics program for that.
Finding, for 1000 * 500 points, which of ca. 5000 points along the contour of the two letters is the nearest one is a lot of computation. Therefore this contains a crude mechanism to only test those contour points that are in the vincinity. Nonetheless, give it a few seconds to complete. If you compute only one letter at that size and halve the canvas size, the execution time will aproximately quarter.
const width = 1000;
const height = 500;
const letter = document.querySelector('path');
const svg = document.querySelector('svg');
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
function isInside(x, y) {
const point = svg.createSVGPoint();
point.x = x;
point.y = y;
return letter.isPointInFill(point);
}
// a 21 * 11 array of arrays
const fields = new Array(21).fill(0).map(() => {
return new Array(11).fill(0).map(() => []);
});
// a list of points along the contour
const length = Math.floor(letter.getTotalLength());
Array.from(new Array(length), (x, i) => {
return letter.getPointAtLength(i);
}).forEach(point => {
// find out if a contour point is inside a 100 * 100 rectangle
let rx1= Math.round(point.x / 100) * 2;
let ry1 = Math.round(point.y / 100) * 2;
// or a 100 * 100 rectangle that is offset by 50
let rx2 = Math.round((point.x + 50) / 100) * 2 - 1;
let ry2 = Math.round((point.y + 50) / 100) * 2 - 1;
// push the point into all four lists for the rectangles it is part of
fields[rx1][ry1].push(point);
fields[rx1][ry2].push(point);
fields[rx2][ry1].push(point);
fields[rx2][ry2].push(point);
});
const data = new Float32Array(width * height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// only handle points inside the contour
if (isInside(x, y)) {
// find out which 50 * 50 rectangle the inside point is part of
const rx = Math.round(x / 50);
const ry = Math.round(y / 50);
// find the nearest contour point from the list for the
// appropriate 100 * 100 rectangle
const d = fields[rx][ry].reduce((min, point) => {
const dist = Math.hypot(point.x - x, point.y - y)
return Math.min(min, dist);
}, 100);
// store that distance value
data[y * width + x] = d;
}
}
}
data.forEach((v, i, a) => {
// find out if the distance to the nearest contour point
// is a local maximum, vertically or horizontally
const vert = a[i - width] < v && a[i + width] < v;
const hor = a[i - 1] < v && a[i + 1] < v;
if (vert || hor) {
// color that point as part of the center line
ctx.fillRect(i % width, Math.floor(i / width), 1, 1);
}
});
<svg width="1000" height="500" style="position:absolute">
<path id="letter" d="M 374.512,316.992 H 220.703 L 193.75,379.687 Q 183.789,402.832 183.789,414.258 183.789,423.34 192.285,430.371 201.074,437.109 229.785,439.16 V 450 H 104.688 V 439.16 Q 129.59,434.766 136.914,427.734 151.855,413.672 170.02,370.605 L 309.766,43.6523 H 320.02 L 458.301,374.121 Q 475,413.965 488.477,425.977 502.246,437.695 526.562,439.16 V 450 H 369.824 V 439.16 Q 393.555,437.988 401.758,431.25 410.254,424.512 410.254,414.844 410.254,401.953 398.535,374.121 Z M 366.309,295.312 298.926,134.766 229.785,295.312 Z M 810.742,247.266 Q 852.051,256.055 872.559,275.391 900.977,302.344 900.977,341.309 900.977,370.898 882.227,398.145 863.477,425.098 830.664,437.695 798.145,450 731.055,450 H 543.555 V 439.16 H 558.496 Q 583.398,439.16 594.238,423.34 600.977,413.086 600.977,379.687 V 123.047 Q 600.977,86.1328 592.48,76.4648 581.055,63.5742 558.496,63.5742 H 543.555 V 52.7344 H 715.234 Q 763.281,52.7344 792.285,59.7656 836.23,70.3125 859.375,97.2656 882.52,123.926 882.52,158.789 882.52,188.672 864.355,212.402 846.191,235.84 810.742,247.266 Z M 657.227,231.445 Q 668.066,233.496 681.836,234.668 695.898,235.547 712.598,235.547 755.371,235.547 776.758,226.465 798.437,217.09 809.863,198.047 821.289,179.004 821.289,156.445 821.289,121.582 792.871,96.9727 764.453,72.3633 709.961,72.3633 680.664,72.3633 657.227,78.8086 Z M 657.227,421.289 Q 691.211,429.199 724.316,429.199 777.344,429.199 805.176,405.469 833.008,381.445 833.008,346.289 833.008,323.145 820.41,301.758 807.812,280.371 779.395,268.066 750.977,255.762 709.082,255.762 690.918,255.762 678.027,256.348 665.137,256.934 657.227,258.398 Z"/>
</svg>
<canvas width="1000" height="500" style="position:absolute"></canvas>

Combining two Matrix Transformations under the same Transformation with SVG

My current task is attempting to combine objects with similar matrices under the same transformation matrix. The two matrices will always have the first 4 digits of it's transform be equal. I am having difficulty calculating the x="???" and y="???" for the second tspan. Any help towards the proper equation would be greatly appreciated.
Input
<svg>
<text transform="matrix(0 1 1 0 100 100)"><tspan x=0 y=0>foo</tspan></text>
<text transform="matrix(0 1 1 0 110 110)"><tspan x=0 y=0>bar</tspan></text>
</svg>
Output
<svg>
<text transform="matrix(0 1 1 0 100 100)">
<tspan x="0" y="0">foo</tspan>
<tspan x="???" y="???">bar</tspan>
</text>
</svg>
EDIT 1
I guess my question is more along the lines of given a point (x,y), how do I apply an existing matrix transformation to that point so that the position will not move, but the element will now be nested inside of another element.
EDIT 2
I have got this code to work for matrices with 0s in the (a,d) or (b,c) positions. Slanted/Skewed matrices I still have not got working. Any thoughts on this?
var aX = floatX[0];
var bX = floatX[1];
var cX = floatX[2];
var dX = floatX[3];
var eX = floatX[4];
var fX = floatX[5];
var aY = floatY[0];
var bY = floatY[1];
var cY = floatY[2];
var dY = floatY[3];
var eY = floatY[4];
var fY = floatY[5];
var xX = (eX * aX) + (fX * bX);
var xY = (eX * cX) + (fX * dX);
var yX = (eY * aY) + (fY * bY);
var yY = (eY * cY) + (fY * dY);
var c1 = cX - aX;
var c2 = dX + bX;
return new float[] { (yX - xX) / (c1 * c2), (yY - xY) / (c1 * c2) };
One thought that may work if my logic isn't flawed, is to find the transform for one element to the other, which can then be used to transform a point of 0,0 (as that's the original x,y) to a new location.
Once we know what the difference in transforms is (assuming that the first 4 figures in the matrix are the same as mentioned in the question, it won't work otherwise), we can figure what the difference in x,y is.
First, there's a bit of code as some browsers have removed this feature..
SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) {
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
This is an svg method that some browsers support, but including as a polyfill in case yours doesn't (like Chrome). It finds the transform from one element to another.
We can then use this, to find the transform from the first to the second text element.
var text1 = document.querySelector('#myText1')
var text2 = document.querySelector('#myText2')
var transform = text2.getTransformToElement( text1 )
Or if you don't want the polyfill, this 'may' work (matrices aren't a strong point of mine!). getCTM() gets the current transformation matrix of an element.
var transform = text1.getCTM().inverse().multiply( text2.getCTM() )
Now we know what the transform between them was. We also know the original x,y was 0,0. So we can create an svg point 0,0 and then transform it with that matrix we've just figured, to find the new x,y.
var pt = document.querySelector('svg').createSVGPoint();
pt.x = 0; pt.y = 0;
var npt = pt.matrixTransform( transform );
Then just a delayed example to show it being moved. Set the tspan with the new x,y we've just figured from the previous transform.
setTimeout( function() {
alert('new x,y is ' + npt.x + ',' + npt.y)
tspan2.setAttribute('x', npt.x);
tspan2.setAttribute('y', npt.y);
},2000);
jsfiddle with polyfill
jsfiddle without polyfill

How to make a circle with SVG Path tag, using Arc attributes [duplicate]

Short question: using SVG path, we can draw 99.99% of a circle and it shows up, but when it is 99.99999999% of a circle, then the circle won't show up. How can it be fixed?
The following SVG path can draw 99.99% of a circle: (try it below and see if you see 4 arcs or only 2 arcs, but note that if it is IE, it is rendered in VML, not SVG, but have the similar issue)
var paper = Raphael(0, 0, 300, 800);
// Note that there are supposed to be 4 arcs drawn, but you may see only 1, 2, or 3 arcs depending on which browser you use
paper.path("M 100 100 a 50 50 0 1 0 35 85").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this is about 62.5% of a circle, and it shows on most any browsers
paper.path("M 100 210 a 50 50 0 1 0 0.0001 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this one won't show anything if it is IE 8's VML, but will show if it is Chrome or Firefox's SVG. On IE 8, it needs to be 0.01 to show
paper.path("M 100 320 a 50 50 0 1 0 0.0000001 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this one won't draw anything at all, unless you change the 0.0000001 to 0.0001 on Chrome or Firefox... Safari will show it though...
paper.path("M 100 430 a 50 50 0 1 0 0 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this is 100% of a circle... even Safari won't show it
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
M 100 100 a 50 50 0 1 0 0.00001 0
But when it is 99.99999999% of a circle, then nothing will show at all?
M 100 100 a 50 50 0 1 0 0.00000001 0
And that's the same with 100% of a circle (it is still an arc, isn't it, just a very complete arc)
M 100 100 a 50 50 0 1 0 0 0
How can that be fixed? The reason is I use a function to draw a percentage of an arc, and if I need to "special case" a 99.9999% or 100% arc to use the circle function, that'd be kind of silly.
Again, a test case is above
(and if it is VML on IE 8, even the second circle won't show... you have to change it to 0.01)
Update:
This is because I am rendering an arc for a score in our system, so 3.3 points get 1/3 of a circle. 0.5 gets half a circle, and 9.9 points get 99% of a circle. But what if there are scores that are 9.99 in our system? Do I have to check whether it is close to 99.999% of a circle, and use an arc function or a circle function accordingly? Then what about a score of 9.9987? Which one to use? It is ridiculous to need to know what kind of scores will map to a "too complete circle" and switch to a circle function, and when it is "a certain 99.9%" of a circle or a 9.9987 score, then use the arc function.
I know it's a bit late in the game, but I remembered this question from when it was new and I had a similar dillemma, and I accidently found the "right" solution, if anyone is still looking for one:
<path
d="
M cx cy
m -r, 0
a r,r 0 1,0 (r * 2),0
a r,r 0 1,0 -(r * 2),0
"
/>
In other words, this:
<circle cx="100" cy="100" r="75" />
can be achieved as a path with this:
<path
d="
M 100, 100
m -75, 0
a 75,75 0 1,0 150,0
a 75,75 0 1,0 -150,0
"
/>
The trick is to have two arcs, the second one picking up where the first left off and using the negative diameter to get back to the original arc start point.
The reason it can't be done as a full circle in one arc (and I'm just speculating) is because you would be telling it to draw an arc from itself (let's say 150,150) to itself (150,150), which it renders as "oh, I'm already there, no arc necessary!".
The benefits of the solution I'm offering are:
it's easy to translate from a circle directly to a path, and
there is no overlap in the two arc lines (which may cause issues if you are using markers or patterns, etc). It's a clean continuous line, albeit drawn in two pieces.
None of this would matter if they would just allow textpaths to accept shapes. But I think they are avoiding that solution since shape elements like circle don't technically have a "start" point.
snippet demo:
circle, path {
fill: none;
stroke-width: 5;
stroke-opacity: .5;
}
circle {
stroke: red;
}
path {
stroke: yellow;
}
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
width="220px" height="220px">
<circle cx="100" cy="100" r="75" />
<path
d="
M 100, 100
m -75, 0
a 75,75 0 1,0 150,0
a 75,75 0 1,0 -150,0
"
/>
</svg>
Update:
If you are using the path for a textPath reference and you are wanting the text to render on the outer edge of the arc, you would use the exact same method but change the sweep-flag from 0 to 1 so that it treats the outside of the path as the surface instead of the inside (think of 1,0 as someone sitting at the center and drawing a circle around themselves, while 1,1 as someone walking around the center at radius distance and dragging their chalk beside them, if that's any help). Here is the code as above but with the change:
<path
d="
M cx cy
m -r, 0
a r,r 0 1,1 (r * 2),0
a r,r 0 1,1 -(r * 2),0
"
/>
Same for XAML's arc. Just close the 99.99% arc with a Z and you've got a circle!
In reference to Anthony’s solution, here is a function to get the path:
function circlePath(cx, cy, r){
return 'M '+cx+' '+cy+' m -'+r+', 0 a '+r+','+r+' 0 1,0 '+(r*2)+',0 a '+r+','+r+' 0 1,0 -'+(r*2)+',0';
}
A totally different approach:
Instead of fiddling with paths to specify an arc in svg, you can also take a circle element and specify a stroke-dasharray, in pseudo code:
with $score between 0..1, and pi = 3.141592653589793238
$length = $score * 2 * pi * $r
$max = 7 * $r (i.e. well above 2*pi*r)
<circle r="$r" stroke-dasharray="$length $max" />
Its simplicity is the main advantage over the multiple-arc-path method (e.g. when scripting you only plug in one value and you're done for any arc length)
The arc starts at the rightmost point, and can be shifted around using a rotate transform.
Note: Firefox has an odd bug where rotations over 90 degrees or more are ignored. So to start the arc from the top, use:
<circle r="$r" transform="rotate(-89.9)" stroke-dasharray="$length $max" />
Building upon Anthony and Anton's answers I incorporated the ability to rotate the generated circle without affecting it's overall appearance. This is useful if you're using the path for an animation and you need to control where it begins.
function(cx, cy, r, deg){
var theta = deg*Math.PI/180,
dx = r*Math.cos(theta),
dy = -r*Math.sin(theta);
return "M "+cx+" "+cy+"m "+dx+","+dy+"a "+r+","+r+" 0 1,0 "+-2*dx+","+-2*dy+"a "+r+","+r+" 0 1,0 "+2*dx+","+2*dy;
}
i made a jsfiddle to do it in here:
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
function describeArc(x, y, radius, startAngle, endAngle){
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");
return d;
}
console.log(describeArc(255,255,220,134,136))
link
all you need to do is to change the input of console.log and get the result in console
For those like me who were looking for an ellipse attributes to path conversion:
const ellipseAttrsToPath = (rx,cx,ry,cy) =>
`M${cx-rx},${cy}a${rx},${ry} 0 1,0 ${rx*2},0a${rx},${ry} 0 1,0 -${rx*2},0 Z`
Adobe Illustrator uses bezier curves like SVG, and for circles it creates four points. You can create a circle with two elliptical arc commands...but then for a circle in SVG I would use a <circle /> :)
Written as a function, it looks like this:
function getPath(cx,cy,r){
return "M" + cx + "," + cy + "m" + (-r) + ",0a" + r + "," + r + " 0 1,0 " + (r * 2) + ",0a" + r + "," + r + " 0 1,0 " + (-r * 2) + ",0";
}
It's a good idea that using two arc command to draw a full circle.
usually, I use ellipse or circle element to draw a full circle.
Another way would be to use two Cubic Bezier Curves. That's for iOS folks using pocketSVG which doesn't recognize svg arc parameter.
C x1 y1, x2 y2, x y (or c dx1 dy1, dx2 dy2, dx dy)
The last set of coordinates here (x,y) are where you want the line to end. The other two are control points. (x1,y1) is the control point for the start of your curve, and (x2,y2) for the end point of your curve.
<path d="M25,0 C60,0, 60,50, 25,50 C-10,50, -10,0, 25,0" />
These answers are much too complicated.
A simpler way to do this without creating two arcs or convert to different coordinate systems..
This assumes your canvas area has width w and height h.
`M${w*0.5 + radius},${h*0.5}
A${radius} ${radius} 0 1 0 ${w*0.5 + radius} ${h*0.5001}`
Just use the "long arc" flag, so the full flag is filled. Then make the arcs 99.9999% the full circle. Visually it is the same. Avoid the sweep flag by just starting the circle at the rightmost point in the circle (one radius directly horizontal from the center).

D3 JS upside down path text

Is it possible to show the text not upside down in this case?
http://jsfiddle.net/paulocoelho/Hzsm8/1/
Code:
var cfg = {
w:400,
h:400
};
var g = d3.select("#testdiv").append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g")
var arct = d3.svg.arc()
.innerRadius(cfg.h / 5)
.outerRadius(cfg.h / 3)
.startAngle(Math.PI/2)
.endAngle(Math.PI*1.5);
var path = g.append("svg:path")
.attr("id","yyy")
.attr("d", arct)
.style("fill","blue")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");
var text = g.append("text")
.style("font-size",30)
.style("fill","#F8F8F8")
.attr("dy",35)
.append("textPath")
.attr("xlink:href","#yyy")
.attr("startOffset",50)
.text("some text")
;
A great example is Placing Texts on Arcs with D3.js by Nadieh Bremer. A lengthy blog with many images from which the following is an extract:
Flipping the Text on the Bottom Half
You could already feel like it’s finished with that look. But I find those labels along the bottom half, that are upside down, rather hard to read. I’d prefer it if those labels were flipped, so I can read them from left to right again.
To accomplish this, we need to switch the start and end coordinates of the current arc paths along the bottom half so they are drawn from left to right. Furthermore, the sweep-flag has to be set to 0 to get the arc that runs in a counterclockwise fashion from left to right
So for the final act, let’s add a few more lines of code to the .each() statement
//Create the new invisible arcs and flip the direction for those labels on the bottom half
.each(function(d,i) {
//Search pattern for everything between the start and the first capital L
var firstArcSection = /(^.+?)L/;
//Grab everything up to the first Line statement
var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
//Replace all the commas so that IE can handle it
newArc = newArc.replace(/,/g , " ");
//If the end angle lies beyond a quarter of a circle (90 degrees or pi/2)
//flip the end and start position
if (d.endAngle > 90 * Math.PI/180) {
var startLoc = /M(.*?)A/, //Everything between the capital M and first capital A
middleLoc = /A(.*?)0 0 1/, //Everything between the capital A and 0 0 1
endLoc = /0 0 1 (.*?)$/; //Everything between the 0 0 1 and the end of the string (denoted by $)
//Flip the direction of the arc by switching the start and end point (and sweep flag)
var newStart = endLoc.exec( newArc )[1];
var newEnd = startLoc.exec( newArc )[1];
var middleSec = middleLoc.exec( newArc )[1];
//Build up the new arc notation, set the sweep-flag to 0
newArc = "M" + newStart + "A" + middleSec + "0 0 0 " + newEnd;
}//if
//Create a new invisible arc that the text can flow along
svg.append("path")
.attr("class", "hiddenDonutArcs")
.attr("id", "donutArc"+i)
.attr("d", newArc)
.style("fill", "none");
});
The only thing that has changed since the previous section is the addition of the if statement. To flip the start and end positions, we can use a few more regular expressions. The current starting x and y location is given by everything in between the capital M and the capital A. The current radius is denoted by everything in between the capital A and the 0 0 1 of the x-axis rotation, large-arc flag and sweep flag. Finally the end location is given by all in between the 0 0 1 and the end of the string (denoted by a $ in regex).
So we save all the pieces in different variables and build/replace up the newArc using the final line in the if statement which has switched the start and end position.
The textPath section needs a small change. For the bottom half arcs, the dy attribute shouldn’t raise the labels above the arc paths, but lower the labels below the arc paths. So we need a small if statement which will result in two different dy values.
(To be able to use the d.endAngle in the if statement I replaced the donutData by pie(donutData) in the .data() step. You can still reference the data itself by using d.data instead of just d, which you can see in the .text() line of code.)
//Append the label names on the outside
svg.selectAll(".donutText")
.data(pie(donutData))
.enter().append("text")
.attr("class", "donutText")
//Move the labels below the arcs for those slices with an end angle greater than 90 degrees
.attr("dy", function(d,i) { return (d.endAngle > 90 * Math.PI/180 ? 18 : -11); })
.append("textPath")
.attr("startOffset","50%")
.style("text-anchor","middle")
.attr("xlink:href",function(d,i){return "#donutArc"+i;})
.text(function(d){return d.data.name;});
It looks like when d3 creates one of those filled arcs it actually creates a filled path shape that always(?) starts on the right and proceeds clockwise - even if you reverse startAngle and endAngle.
If you manually create your own arc path, and put your text on that, you can get it to do the right thing.
var cfg = {
w:400,
h:400
};
var g = d3.select("#testdiv").append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g")
var arct = d3.svg.arc()
.innerRadius(cfg.h / 5)
.outerRadius(cfg.h / 3)
.startAngle(Math.PI/2)
.endAngle(Math.PI*1.5);
var path = g.append("svg:path")
.attr("id","yyy")
.attr("d", arct)
.style("fill","blue")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");
// Radius of line text sits on. A value of 3.5 makes it slightly closer to the
// outer radius (so text is placed in the middle of the blue line).
var textpathRadius = (cfg.h / 3.5);
// Make a path for the text to sit on that goes in an anti-clockwise direction.
var textpath = g.append("svg:path")
.attr("id","zzz")
.style("display","none")
.attr("d", "M -"+textpathRadius+" 0 A "+textpathRadius+" "+textpathRadius+" 0 0 0 "+textpathRadius+" 0")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");
var text = g.append("text")
.style("font-size",30)
.style("fill","#F8F8F8")
.attr("dy",0)
.append("textPath")
.attr("xlink:href","#zzz")
.attr("startOffset","50%")
.style("text-anchor","middle")
.text("some text");
I've never used d3 before so there might be an easier or cleaner way to do what I've done. But at least it should give you a place to start.
Updated fiddle: http://jsfiddle.net/3DfVD/

Resources