ImageMagick & Pie Charts - geometry

What is the most direct way to draw a pie chart using ImageMagick in an existing image. For example, how would I draw a single slice given the following inputs?
A center point (x,y)
Radius
Percent

Create your pie wedge using SVG; I got my example from here:
<svg>
<path d="M200,200 L200,20 A180,180 0 0,1 377,231 z"
style="fill:#ff0000;
fill-opacity: 1;
stroke:black;
stroke-width: 1"/>
</svg>
Then, overlay that image using ImageMagick to your background image.
composite.exe -background none -size 200x200 .\pie_wedge.svg .\background.png out.png
Note that you have to define your arcs with cartesian coordinates instead of radius, but the conversion should be fairly straightforward.

Related

How to draw a responsive SVG path with points on it?

I am trying to draw this SVG path.
I could achieve it using SVG Line and Curve properties for a fixed height and width using fixed coordinates.
But, I need to draw this responsively which means, the width of the path, space between the lines, the distance between each point, and the curves at the sides should be responsive.
It contains levels indicated by numbers as shown in the image, and the length of the path should be determined by the number of levels given.
While trying to draw this responsively, I was stuck at
Getting the start and end-point of the Lines
Getting control points for the curves
Responsive adjustment of the distance between each point and space between the curves
Determining the length of the path based on the number of levels given
I am trying to do these using percentages based on the parent div's width by some mathematical calculations. But, it seems it breaks some or other cases due to its complexity.
There are some other things to do along with these, but this is the top-level version of what needs to be done.
Is there any direct method or formula or calculation for achieving this?
(or)
Is there any other approach to draw this?
(or)
Are there any tutorials for learning to draw these types of SVG paths?
After creating the path you need to calculate the position of the numbers and circles on the path. For this you need to know the length of the path calculated with the getTotalLength() method. Next you need a loop to calculate the position of the numbers on the path. For this I've used the getPointAtLength()
method. On each of this pointd you create a new circle (use) element and a new text element.
Please read the comments in the code.
//constants used to create a new element in svg
const SVG_NS = "http://www.w3.org/2000/svg";
const SVG_XLINK = "http://www.w3.org/1999/xlink";
//the total length of the path
let length= thePath.getTotalLength();
//the number of points on the path
let n = 15;
let step = length/n;
for(let i= 1; i<=n; i++){
//creates a new point on the path at a given length
let point = thePath.getPointAtLength(i*step);
//create a new use element (or a circle) and set the center of the circle on the calculated point
let use = document.createElementNS(SVG_NS, 'use');
use.setAttributeNS(SVG_XLINK, 'xlink:href', '#gc');
use.setAttribute("x", point.x);
use.setAttribute("y", point.y);
//create a new text element on the same point. Thr text content is the i number
let text = document.createElementNS(SVG_NS, 'text');
text.setAttribute("x", point.x);
text.setAttribute("y", point.y);
text.textContent = i;
//append the 2 new created elements to the svg
svg.appendChild(use);
svg.appendChild(text);
}
svg {
border: solid;
}
text {
fill: black;
font-size: 4px;
text-anchor: middle;
dominant-baseline: middle;
}
<svg viewBox="0 0 100 80" id="svg">
<defs>
<g id="gc">
<circle fill="silver" r="3" />
</g>
</defs>
<path id="thePath" fill="none" stroke="black" d="M10,10H70A10,10 0 0 1 70,30H20A10,10 0 0 0 20,50H70A10,10 0 0 1 70,70H20" />
</svg>
Please observe that since the svg element has a viewBox attribute and no with it takes all the available width making it responsive.

Translate SVG circle coordinates to the leaflet geographical coordinate system

I'm working a proof-of-concept app that shows a real-time map of the metros in Brussels (see it). I created the map using Adobe Illustrator, and I use a circle to represent each stop. For the map view, I'm using leafletjs.
For example, this a simplified version of the svg map with a circle.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5457 4961">
<defs><style>.a{fill:#fff;stroke:#000;stroke-miterlimit:10;}</style></defs>
<title>Simple Map</title>
<circle class="a" cx="1957.5" cy="1399.5" r="4.5"/></svg>
And this is the definition of my leafletjs map
var map = L.map('map', {
crs: L.CRS.Simple,
zoomDelta: 0.50,
zoomSnap: 0,
minZoom: -1,
maxZoom: 1.5 ,
touchZoom: true,
fullscreenControl: true,
});
var bounds = [[0,0], [1000,1000]];
var image = L.imageOverlay('./images/map.svg', bounds).addTo(map);
My question is:
Could be possible to translate the cx and cy of the svg circle to the leaflet geographical coordinate system?
I need to put a leaflet marker at all the stops where is a metro. Right now, for the only line showed on my map I obtained the coordinates by hand :(
Thanks in advance!
Humberto
you can use this to move your circle
where x and y are the x-axis and y-axis cordinates respectively
$('rect').attr("transform","translate("+x+","+y+")");

SVG - How to apply a gradient transform matrix to a linear gradient brush?

I'm writing a SVG viewer application in c++ language. I actually face a transform issue in several SVG files that I cannot figure out.
Considering the following SVG file:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg id="svg9686" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="90mm" width="145mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 145 90" xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs id="defs9680">
<linearGradient id="linearGradient6593-0" gradientUnits="userSpaceOnUse" x1="74.658" y1="-398.92" x2="75.519" y2="-485.7" gradientTransform="matrix(1.0069 0 0 1.19 1.4571 709.77)">
<stop id="stop6595" stop-color="#be245a" offset="0"/>
<stop id="stop6600" stop-color="#e46e6f" offset=".48408"/>
<stop id="stop6597" stop-color="#f1a769" offset="1"/>
</linearGradient>
</defs>
<g id="layer1" transform="translate(-7.631 -139.36)">
<rect id="rect3690-4-2-09-4-2-8-0" height="90" width="145" y="139.36" x="7.631" fill="url(#linearGradient6593-0)"/>
</g>
</svg>
This is basically a rectangle filled with a gradient brush, from orange to magenta. The rectangle size is 90x145, located at coordinates [0, 0] after all the transformations are applied.
If I well understood the theory, to draw the rectangle correctly, I should process the following steps:
Calculate the gradient brush bounding box, given by the x1, y1, x2 and y2 values, in the local document coordinates system. This should be done by applying the given gradient transform matrix to the points computed from x1, y1, x2 and y2
As the gradient units are declared to be "user space on use", calculate the brush from the linear gradient tag values and the transformed bounding box
Transform the rectangle coordinates to fill, to also put it in the document coordinates system
Using the previously created brush, draw the rectangle at his transformed coordinates
Applying the above described process, I expected to reach the following result:
But I get this result:
If I manually change all the values in the above source file, to remove all transformations and apply all the values in the document coordinates, the linear gradient is filled correctly in my rectangle. For that reason, I would be really thankful if somebody could explain to me
What I'm doing wrong in my process?
How I should calculate the linear gradient values?
How should I apply the gradient matrix to the given values? (i.e. I expected that applying the matrix to the values should transform them in the document system coordinates, so the transformed values should roughly give x1 = 0, y1 = 0, x2 = 90 and y2 = 145 as result, but it's not the case)
NOTE a demonstration in mathematical form would be welcome
First, using the term "bounding box" for the gradient I think is not helpfull. The four values x1, x2, y1, y2 describe a vector onto which the gradient stops are matched, and to which the gradient normal is perpendicular (before applying any transform). A "box" has no meaningfull relation to the gradient properties.
The gradient vector could be visualized as a line element
<line x1="74.658" y1="-398.92" x2="75.519" y2="-485.7" />
The first step is to apply gradientTransform="matrix(1.0069 0 0 1.19 1.4571 709.77)". The resulting line would be drawn as
<line x1="76.6302" y1="235.055" x2="77.4972" y2="131.787" />
(Since the transformation introduces no skew, the gradient normal is still perpendicular to that line.)
At this point, the gradient is applied to the rectangle in its local coordinate system
<rect width="145" height="90" x="7.631" y="139.36"/>
<line x1="76.6302" y1="235.055" x2="77.4972" y2="131.787" />
Only after that, the final transform="translate(-7.631 -139.36)" is applied to both the rectangle and the vector:
<rect width="145" height="90" x="7.631" y="139.36"/>
<line x1="70" y1="95.7" x2="69.87" y2="-7.57" />
Note that this would even be true if the transform had been applied to the rect directly, instead of an enclosing group. Applying the transform to an element is always the last operation to perform.
Where you went wrong, I think, is interpreting userSpaceOnUse as the final coordinate system after applying transforms to the rectangle. But what it is, is the coordinate system
in place at the time when the gradient element is referenced,
thus before further transformations.

How to convert font size (in px) to the unit used in the document (mm) for svg

I have svg created in inkscape here.
The document size is in mm. For the 2 text fields, the font size is given like this:
style="...;font-size:31.420084px;..."
So it is given in px. Now I want the size of the font related to the document size.
But how should I convert the font-size in px to mm? I would need something like dpi, but the document does not specify anything.
In your case, your viewBox matches your document dimensions:
width="164.28572mm"
height="78.10714mm"
viewBox="0 0 164.28572 78.10714"
that means all unit less values are in mm. Just specify your font-size without units (or in your case when you use css just keep your document untouched and use px).
The confusing part is, that px in svg are always user units and not device pixels, for that reason, your font-size is already given in mm... so font-size:31.420084px in your document is equal to font-size:31.420084mm in a viewBox less document (where user units equal to device pixels)
<svg viewBox="0 0 100 20" width="100mm" height="20mm">
<text x="0" y="12" font-size="12px">12px</text>
</svg>
<svg >
<text x="0" y="12mm" font-size="12mm">12mm</text>
</svg>
Thats where it gets confusing. In the next example "1mm" is equal to 3.6 user units, but because 1 user unit equals 1mm in the real world, one svg mm equals 3.6 real mm ...
<svg viewBox="0 0 50 50" width="50mm" height="50mm">
<text x="0" y="5" font-size="1mm">font-size: 1mm</text>
<text x="0" y="10" font-size="3.6">font-size: 3.6</text>
</svg>
Units in SVG are a bit wired, but well defined...
The SVGLength Interface has a Method convertToSpecifiedUnits.
you can use that to convert between different units in SVG.
var l = svg.createSVGLength()
l.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, 12)
l.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_MM)
out1.innerHTML = l.valueAsString
svg {
width: 0;
height: 0
}
<svg id="svg">
</svg>
12px = <span id="out1"></span>
The standard DPI used in an SVG document is the standard DPI that is specified by the CSS standard. That is 96dpi, or 96 standard CSS pixels per inch.
There are 25.4 mm per inch. So to convert px to mm, the formula will be:
mm = 25.4 * (px / 96)
So if we plug your original 31.420084px into that formula, the result is 8.31mm.
Note that CSS doesn't take into account the real word DPI of the device that you are rendering to. It uses that fixed approximation of 96pixels per inch. So you can't rely on the fact that an element with size 96px or 25.4mm will actually be that size on screen, or when printed.

Drawing parts of circle's circumference in SVG

I need to draw in SVG parts of circle's circumference in different colours (please look at the picture below). Unfortunately I'm not good at SVG, so I found solution which is in my opinion very poor: I'm using describeArc() function from this post and I draw two paths - one colourful circle and, over it, white circle with smaller radius. For example:
PATH1 = describeArc(x=10000, y=5000, radius=3000, startAngle=45, endAngle=90)
PATH2 = describeArc(x=10000, y=5000, radius=2800, startAngle=45, endAngle=90)
and after inserting it into svg, I have:
<path d=PATH1 fill="red" stroke-width="0" />
<path d=PATH2 fill="white" stroke="white" stroke-width="100" />
It works, but for me it's the ugly solution. How can I do it better?
That function is called describeArc(), but is actually drawing a circular sector, not an arc.
I agree it is kind of ugly because you are unnecessarily drawing - and overdrawing - most of the sector. And you will get issues with some of the colours bleeding around the edge of the white. I assume that's why you are including the white stroke.
The solution is very simple. Change the describeArc() function so that it is only drawing the arc, and not the rest of the sector. Change the code as follows:
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, arcSweep, 0, end.x, end.y
].join(" ");
Ie. remove the two "L" path commands that form the other two sides of the sector. In other words, use the answer by #opsb, not the #Ahtenus answer.

Resources