Get pixel coordinates of a path - javascript

A typical path element under svg will be like
<svg width="960" height="960">
<g transform="translate(480,480)">
<path style="fill:none; stroke:#000"
d="M69.963,-353.136
L66.190,-341.705
C62.417,-330.274,54.870,-307.412,46.957,-284.620
C39.043,-261.828,30.762,-239.107,28.338,-191.600
C25.915,-144.093,29.348,-71.801,1.420,-46.863
C-26.507,-21.924,-85.794,-44.340,-128.969,-48.510
C-172.144,-52.680,-199.207,-38.604,-234.999,-29.311
C-270.791,-20.018,-315.311,-15.509,-337.572,-13.255
L-359.832,-10.100">
</path></g></svg>
Now I need to get the coordinates of the pixels in the Bézier curve. I understand I can calculate the coordinates by the definition of Bézier curve. But are there any other more convenient ways (like some ready-made routines)?

Related

how to create d3 radial with dynamic radios

I created a radial with two tiers of options. I did in a way that isn't really dynamic and isn't really responsive to screen size. I now need it to be both of those things. Here is what it looks like when on the screen size I designed it for.
I created a working demo on sandbox that has the dimensions set how I need to use it on. This is what it looks like.
Here is link WORKING DEMO
any help is appreciated. Also keep in mind the outer tiers can have less or more options. it would be great if the blue toggle button would always align at the bottom of the radial like under the En of Energy Loss
I would consider using an SVG ViewBox in order to maintain consistency. What this basically does is create a consistent scalable SVG, mapping the size and coordinates of its container into a consistent range inside the SVG.
For example:
<div height="400px" width="400px">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="100%" height="100%" stroke="red" fill-opacity="0"/>
<circle r="4" cx="10" cy="10"/>
</svg>
</div>
So it basicalley creates a mapping from the 400x400 dimensions of the div, into the 100x100 of the svg, so the circle positioned at (10, 10) inside the svg will actually be in coordinates (40, 40) of the div

SVG animate path to rotate around its center

I want to animate an image for The Center for Humane Technology for use on a html landing page. The image looks like this and contains 28 gears positioned in a heart shape. I would like to have each gear to rotate continuously around its center, either clockwise or counter-clockwise.
I have read the other SO posts that deal with similar issues, but the solutions do not work for me. When I add e.g. an animateTransform to the shape of a gear, specifying its center coordinates, then it rotates in a wide circle around its center, not staying into position. I am confused.
I have the orginal artwork (created by nivedita) from EPS to SVG, which resulted in rather large paths. This is the resulting SVG image.
First I was planning to use AnimateJS for the animation, but inline SVG animation markup may also do the trick (maybe better).
Reading other SO submissions, I tried rotating gear1 by calculating its center from these coordinates:
X: 42.623
Y: 309.810
Width: 60.796
Height: 60.774
Resulting in following transform:
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" id="gears-of-the-heart" width="333.92" height="526.28">
<g id="canvas" transform="matrix(1.25 0 0 -1.25 0 526.27)">
<g id="heart">
<path d="M58.42 265.7a6.41 6.41 ..." id="gear1" fill="#1e2227">
<animateTransform attributeType="xml" attributeName="transform" type="rotate"
from="0 73.111 340.197" to="360 73.111 340.197" additive="sum" dur="3s" repeatDur="indefinite"/>
</path>
<!-- The other 27 gears here -->
</g>
<!-- More SVG elements (unrelated) -->
</g>
</svg>
Its not working. Is this because of the transform on the parent canvas group?
Two questions:
What am I doing wrong?
Is there an easier way to animate this, so I don't have to calculate center for all gears (I know I could use javascript for coordinates calc)?

Scale the svg group path from center of the svg width and height [duplicate]

I have the following SVG graphic:
<svg version="1.1" id="diagram" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="375px" height="150px">
<path d="M45,11.5H33.333c0.735-1.159,1.167-2.528,1.167-4C34.5,3.364,31.136,0,27,0s-7.5,3.364-7.5,7.5c0,1.472,0.432,2.841,1.167,4H9l-9,32h54L45,11.5z M22.5,7.5C22.5,5.019,24.519,3,27,3s4.5,2.019,4.5,4.5c0,1.752-1.017,3.257-2.481,4h-4.037 C23.517,10.757,22.5,9.252,22.5,7.5z" id="control"/>
</svg>
I want to programmatically change the scale of this object, but I want it to scale from the center point.
I've tried wrapping it around a <g> tag, like so
<g transform="translate(0,0)">
<path x="0" y="0" id="control" transform="scale(2)">...</path>
</g>
But this doesn't seem to work. It seems that scaling a path requires manipulation of the path's matrix, which seems horrifically difficult. Annoyingly, it's easy to scale using additive="sum" property but in this instance, I am not using a transform animation.
Can anyone help me out?
Edit: Managed to get this working nicely, for anyone who is stuck on the same thing, here is a nice way of doing it programmatically:
var elem = document.getElementById("control");
var bBox = elem.getBBox();
var scaleX = 2;
var scaleY = 2;
$(elem).attr("transform", `scale(${scaleX}, ${scaleY}) translate(${-bBox.width/2},${-bBox.height/2})`);
If you know the coordinates of the center point, then you can combine a translate and scale in one transformation. The translation is calculated as: (1 - scale) * currentPosition.
If the center is (10, 20) and you are scaling by 3 then translate by (1 - 3)*10, (1 - 3)*20 = (-20, -40):
<g transform="translate(-20, -40) scale(3)">
<path d="M45,11.5H33.333c0.735-1.159,1.167-2.528,1.167-4C34.5,3.364,31.136,0,27,0s-7.5,3.364-7.5,7.5c0,1.472,0.432,2.841,1.167,4H9l-9,32h54L45,11.5z M22.5,7.5C22.5,5.019,24.519,3,27,3s4.5,2.019,4.5,4.5c0,1.752-1.017,3.257-2.481,4h-4.037 C23.517,10.757,22.5,9.252,22.5,7.5z" id="control"/>
</g>
The transformations are applied in reverse order from the one they are declared, so in the example, above, the scale is performed first and then the translate. Scaling affects the coordinates so the translation here is in scaled coordinates.
You can calculate the center point programmatically using element.getBBox().
You can alter the origin to center:
.scaled-path-svg {
svg {
path {
transform-origin: center;
transform: scale(1.1);
}
}
}
The answer provided by aetheria earlier is great. There is another thing to take care of as well -- stroke-width, so that the outline stays of the same width while the object scales. Usage:
stroke-width: (1/scaling-factor)
So, if your scaling is by say 2, then:
stroke-width: (0.5)
NOTE: You shouldn't missout the transform: translate(...) scale(2) as mentioned by aetheria.

How to get position of empty SVG group?

I'm working with d3, and I have trouble with positioning and empty groups.
I have this svg:
<svg id="mysvg" height="200" width="1350" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(50, 6)">
<g class="a">
<g transform="translate(0,161.3919677734375)" style="opacity: 1;">
<line y2="0" x2="-6"></line>
</g>
</g>
<g class="b">
</g>
</g>
</svg>
I want to dynamically add a line to group with class b, and I want to add it so the coordinates of the first point of the line will coincide with the coordinates of the second point of the line inside group a.
Since coordinates of SVG objects are relative to their containers, to get the relative coordinates I get first the absolute position of the line inside g.a and the absolute position of g.b, using getBoundingClientRect()
The problem is coordinates of g.b, if it's empty, are completely messed up. I have to create a bogus object to get them properly:
d3.select("#mysvg .b").append("circle").attr("r", 0).attr("fill", "transparent")
.attr("cx", 0).attr("cy", 0);
Furthermore, if I create the circle with a radius greater than zero, the position of its group b will shift.
You can use basic DOM interfaces to transform between both coordinate systems. Interface InterfaceSVGLocatable defines method getTransformToElement() which
Returns the transformation matrix from the user coordinate system on the current element (after application of the ‘transform’ attribute, if any) to the user coordinate system on parameter element (after application of its ‘transform’ attribute, if any).
It is worth noting, that support for this was dropped from Chrome 48+ (Issue 524432). However, there is a rather slim polyfill available:
// Polyfill needed for Chrome 48+
SVGElement.prototype.getTransformToElement =
SVGElement.prototype.getTransformToElement || function(elem) {
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
Because it is just this one simple line, it might even be easier to directly use it within your code.
You may than use a helper SVGPoint to get the transformation matrix needed to transform from elements in your group a to elements in group b:
// Create a helper point
var point = document.getElementById("mysvg").createSVGPoint();
point.x = line.getAttribute("x2");
point.y = line.getAttribute("y2");
// Calculate starting point of new line based on transformation between coordinate systems.
point = point.matrixTransform(line.getTransformToElement(groupB));
Have a look at this working example, which draws a red line in group b starting at the end of the line in group a to coordinates (100, 100):
// Polyfill needed for Chrome >48
SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) {
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
var groupB = document.querySelector("g.b");
var line = document.querySelector(".a line");
// Create a helper point
var point = document.getElementById("mysvg").createSVGPoint();
point.x = line.getAttribute("x2");
point.y = line.getAttribute("y2");
// Calculate starting point of new line based on transformation between coordinate systems.
point = point.matrixTransform(line.getTransformToElement(groupB));
// D3 applied to simplify matters a little.
// New line is drawn from end of line in group a to (100,100).
d3.select(groupB).append("line")
.attr({
"x1": point.x,
"y1": point.y,
"x2": 100,
"y2": 100
});
line {
stroke: black;
}
.b line{
stroke:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg id="mysvg" height="200" width="1350" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(50, 6)">
<g class="a">
<g transform="translate(0,161.3919677734375)" style="opacity: 1;">
<line y2="0" x2="-40"></line>
</g>
</g>
<g class="b">
</g>
</g>
</svg>
Because questions were raised in the comments regarding the performance of this approach compared to the one proposed by the question itself, I have set up a little jsPerf test case to compare both. I would suspect this answer's code to significantly outperform the original one because manipulating the DOM is always an expensive operation. The matrix calculations on the other hand will only have to use values from the DOM without applying any modifications. The results clearly back this assumption with the insertion of a circle being at least 95% slower in FF, IE and Chrome.

Svg polygon rounding

I am working on an application that is using svg move/rotate/zoom functionalities. I'm programming the back-end in Laravel and the front-end is using html/css/javascript. I've seen on the web that is possible for a polyline to have some sort of cubic-bezier to it.
Now my question is: is it possible for a polygon svg element to have the same cubic-bezier to it as the polyline like in this example?
The structure of the svg looks like is:
<svg>
<g data-type="track">
<polygon class="track" points="2588,851 2537,1157 1796,916 1117,723 0,382 40,80 816,314 1885,638 1887,634"></polygon>
<polygon class="track" points="114,19 73,0 17,497 46,485"></polygon>
</g>
</svg>
Is it possible to give the polygon element a cubic bezier so that it can create a fluid polygon instead of the square no-rounded polygon?
I think some of the responses here have been a little confusing.
(is it) possible for a polygon svg element to have the same cubic-bezier to it as the polyline
The short answer is no. <polygon> (and <polyline>) elements are always rendered as a sequence of straight line segments between the coordinates you provide. There is no way to automatically make the joins have a radius - like an HTML border-radius. If that is what you are asking.
If the line has a bigger stroke width, you can choose to round the outside corner of the line joins.
.track {
fill: none;
stroke: black;
stroke-width: 20;
}
.round {
stroke-linejoin: round;
}
<svg width="300" height="300">
<polygon class="track" points="20,20 290,20 290,130 20,130"></polygon>
<polygon class="track round" points="20,170 290,170 290,280 20,280"></polygon>
</svg>
If you want to include bezier curve segments in your "line", you will have to use the <path> element instead. As was used in the example you linked to.
I suggest to put one duplicated figure above another one with just smaller stroke-width. Profit! :)
<svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
<polygon points="50,30 55,50 70,50 60,60 65,75 50,65 35,75 40,60 30,50 45,50" stroke-linejoin="round" stroke-width="50" stroke="red"/>
<polygon points="50,30 55,50 70,50 60,60 65,75 50,65 35,75 40,60 30,50 45,50" stroke-linejoin="round" stroke-width="30" stroke="#fff"/>
</svg>
A polygon does not use cubic Bézier curves, a path does. The example linked does not use any polygons, but a path which includes such curves.
The difference between a polyline and a polygon is simply that the latter is closed, so you can simply create a path and close it (implicitly or explicitly).
Beyond that, I'm not sure what your actual issue is.

Categories

Resources