How to scale SVG elements separately. - d3.js

I am making a graph in D3, and I am trying to scale the Y axis of the graph on window resize, without scaling the size of the elements within it. (And I don't want to have to redraw the graph).
To say this another way, I want to be able to use SVG to scale the distance between objects, without changing the size of the objects.
Reading the last part of this article and seems to suggest I can, but I can't seem to get the details right.
Here is an simplified codepen of what I am trying to do:
https://codepen.io/thesuperuser/pen/BmdNwz/
<symbol id="circle" width="20" height="20" preserveAspectRatio="none">
<circle cx="10" cy="10" r=10></circle>
</symbol>
and then I use this symbol, and try and preserve the size of the symbol with d3
let svg2 = d3.select('svg.scaled')
svg2.selectAll('use')
.data(data)
.enter()
.append('use')
.attrs({
class: 'circle',
href: '#circle2',
x: d => scale(d),
y: '50%',
height: dotR*2,
width: dotR*2,
})
This seems to just duplicate the behavior in the first example, which scales both size and distance the way you would expect viewBox and preserveAspectRatio to behave.

If you set a viewBox for the svg element, it will scale that viewBox including all content to fit the viewport. What you want is the opposite: a svg element where content positioning can vary, but nothing gets scaled. The key to this is relative (percentage) units.
Here is a working pattern:
<svg width="100%" height="200">
<symbol viewBox="0 0 20 20" preserveAspectRatio="xMidYMid meet">
<circle id="circle" cx="10" cy="10" r="10" />
</symbol>
<use xlink:href="#circle" x="5%" y="50%" width="10%" height="20" />
<use xlink:href="#circle" x="15%" y="50%" width="10%" height="20" />
...
</svg>
This way, when the width of the SVG changes, the viewports of the use elements change their width, but not their height. The circles rendered inside them fit the constant height (as long as the viewports are wider than high). preserveAspectRatio="xMid..." and the viewport x and width result in the circle being centered at 10%, 20%, ... Change these to position the circles as needed.
Here's a codepen to demonstrate with d3.

Related

How can I scale a raster image to user space units in SVG?

My Question
Given a raster image where I know how many pixels (px) correspond to a centimeter (cm) and an SVG path where I define the userspace units to be millimeters inside a viewbox that depends on the browser width: How can I scale the image so that it always has the same pixel per centimeter ratio as the SVG path?
Background
I have raster images that represent fabric textures that I want to match with svg shapes that represent a part of a piece of clothing. I want to overlay the fabric on top of the shape and for it took realistic the proportions of the shape and of the image have to match. This is all done dynamically meaning I want to solve this for the general case not just for one specific image and shape combination.
Example
I've adapted my generic and general code to this specific JS Fiddle to illustrate the problem and show my progress so far as well as where I am stuck:
http://jsfiddle.net/nx3vrz94/16/
<svg id="svgView-1" style="width:90vw" viewBox="0 0 635 334" >
<path fill-opacity="1" fill="url(#fabric1001)" d="M0 0 L635 0 L334 290 Z " id="1"></path>
<!--
Image: 1440x1080
horizontally:
2,5 cm = 51 pixel
= 20,4 pixel per cm
-->
<defs>
<pattern id="fabric1001"
patternContentUnits="objectBoundingBox"
viewBox="0 0 1 1"
preserveAspectRatio="xMidYMid slice"
width="100%" height="100%">
<image xlink:href="https://cdn.shopify.com/s/files/1/0081/1076/8185/products/3.jpg?v=1540031315"
x="0" y="0"
width="1"
height="1"
preserveAspectRatio="xMidYMid slice" />
</pattern>
</defs>
</svg>
So far I managed to add the image as a pattern and then fill the shape with an URL reference. What I don't understand is how I can tell SVG that 20.4 pixels in that image correspond to 10 user space units?
Make your life easier and do not try to first fit an image into a pattern, and then to fit that pattern repeatedly (at least conceptually) into a path, so that you have a total of three implicit size transformations to keep track of, when all you need is to size the image correctly.
If I understand you correctly, your userspace units should equal 1mm. Then, when
2.04 image pixel equals 1mm equals 1 userspace unit
your image needs to be scaled by 1 / 2.04
making your image scale from 1440 × 1080 to 706 × 529 (rounded, the preserveAspectRatio will take care of the rest)
Instead of fitting a "pattern" fill inside the path, you can also cut the path shape out of the image with a clip-path. - It is actually the same process as cutting a part out of a cloth, btw…
<svg id="svgView-1" style="width:90vw" viewBox="0 0 635 334" >
<clipPath id="clip">
<path d="M0 0 L635 0 L334 290 Z "></path>
</clipPath>
<image xlink:href="https://cdn.shopify.com/s/files/1/0081/1076/8185/products/3.jpg?v=1540031315"
x="0" y="0"
width="706" height="529"
preserveAspectRatio="xMidYMid slice"
clip-path="url(#clip)"/>
</svg>

Line draw svg in Anime.js long delay and line missing

I want to draw a triangle in a triangle with Animejs. The triangle should be drawn very slow. Sadly I get a long delay, before the triangle starts to be drawn and on side of the triangle is missing.
My triangle svg:
<div id="lineDrawing"> <svg viewBox="0 0 280 100">
<g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-width="8" class="lines">
<path d="m 29.188247,410.49128 338.094613,0 L 198.23555,47.968205 29.188247,410.49128 Z" stroke-dasharray="300" style="stroke-dashoffset: 316.855px;">
</g>
</svg> </div>
My js:
anime({
targets: '#lineDrawing path',
strokeDashoffset: [anime.setDashoffset, 0],
duration: 80000,
delay: 0,
direction: 'alternate',
loop: true
});
In the end I want to make it look like one of the cornor triangles.
Sorry for any bad js,css or html errors. I'am new to webdesign.
https://youtu.be/JTNgpQWcDIA?t=1m1s
Your SVG triangle seems way out of the viewBox for the most part. The invisible part is also animated, so it seems like nothing is happening.

Shorten SVG path arc by pixel

I have a path with an arc that points to a circle:
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">
<path d="M10,10 A120,120 0 0,0 200,100" stroke="green" stroke-width="5" fill="none" />
<circle cx="200" cy="100" r="10" stroke="black" stroke-width="5" fill="none" />
</svg>
Now I want that the path ends e.g. 20 pixel (or a few degree, if simpler) before the border of the circle:
How can I archive this? How can I calculate the differing X and Y as target for the arc drawing (in my example the 200,100 in the d argument)?
In the end I will do this with D3, so I need an algorithm.
This could easily be done by applying a variation of the stroke-dasharray trick. You can obtain the total length of the path by calling .getTotalLength(), subtract the length you want the path to end before this calculated length, and set the stroke-dasharray attribute accordingly:
var path = d3.select("path")
.attr("stroke-dasharray", function() {
return this.getTotalLength() - 20;
});
<script src="https://d3js.org/d3.v4.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">
<path d="M10,10 A120,120 0 0,0 200,100" stroke="green" stroke-width="5" fill="none" stroke-dasharray="100"/>
<circle cx="200" cy="100" r="10" stroke="black" stroke-width="5" fill="none" />
</svg>
If you want to do this via a geometric algorithm, you will have to first compute the arc's properties such as center (cx, cy), start angle, sweeping angle,...etc from the current available data: radius = 120, start point (10, 10), end point (200, 100) and the two flags (large arc flag and sweep flag). Please refer to section F6.5 in this link for details. Once you have these information, you can compute the new end point easily.

how to make svg animation scaling around a center of a figure?

I'm trying to pulsate a star by using animateTransform with type="scale", but instead of scaling around a center of the star, it scales to left and down. How to scale the star around the center?
Svg code:
<svg version="1.1" id="star" xmlns="http://www.w3.org/2000/svg" x="170px" y="385px" width="100px" height="100px" viewBox="0 0 60 60" xml:space="preserve">
<g>
<polygon fill="#DAC978" points="18.832,36.694 5.903,25.156 23.146,23.419 30.125,7.557 37.104,23.419 54.347,25.155 41.417,36.694 45.095,53.629 30.125,44.898 15.155,53.63" />
<path fill="#A58144" d="M30.125,13.765l4.104,9.327l1.175,2.669l2.901,0.292l10.139,1.021l-7.603,6.785L38.665,35.8l0.618,2.851 l2.162,9.957l-8.801-5.134l-2.519-1.469l-2.52,1.469l-8.803,5.134l2.162-9.957l0.619-2.85l-2.175-1.942l-7.603-6.785l10.139-1.021 l2.901-0.292l1.174-2.669L30.125,13.765 M30.125,1.35l-8.681,19.729L0,23.237l16.08,14.352l-4.574,21.063l18.62-10.858 l18.618,10.858L44.17,37.589l16.081-14.352l-21.445-2.159L30.125,1.35L30.125,1.35z" />
<animateTransform
attributeType="xml"
attributeName="transform"
type="scale"
from="0"
by="1"
dur="1s"
repeatCount="10"
/>
</g>
</svg>
jsFiddle with working code.
animateTransform type scale seems to scale always from origin at (0, 0). One possible solution can be in translating your polygon so that its center is in (0, 0).
Here is working jsFiddle example.
Changes made to your original code:
The viewbox is changed from viewBox="0 0 60 60" to viewBox="-30 -30 60 60" in order to have center of the viewBox at (0, 0) (for more on viewBoxes you can see this tutorial)
polygon and path are grouped and then translated so that their center is at (0, 0) too:
<g transform="translate(-30,-30)">
<polygon ... />
<path ... />
</g>

How to specify the x-y rotation point when using animateTransform?

I want to use animateTransform to rotate an SVG image continuously. So here we go:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="1024px" height="768px" enable-background="new 0 0 100 100" xml:space="preserve">
<g transform="translate(100, 100)">
<rect fill="#FE9FFF" width="100px" height="100px">
<animateTransform attributeName="transform" type="rotate"
from="0" to="360" dur="20s" repeatDur="indefinite"/>
</rect>
</g>
</svg>
This works.
Now: I would like to change the above, so that the block rotates around its center and not its top left corner. I know that if I want to rotate the block statically around its center, I can do this:
<g transform="rotate(30, 50, 50)">
<rect fill="#FE9FFF" width="100px" height="100px">
</rect>
</g>
My question is - how do I manage a continuous animated rotation around the block's center? I have looked at the spec and a couple of other related questions on SO, but I'm having trouble implementing the explanations supplied.
Thanks in advance.
http://www.w3.org/TR/SVG/animate.html#AnimateTransformElement
The ‘from’, ‘by’ and ‘to’ attributes take a value expressed using the same syntax that is available for the given transformation type:
(...)
For a type="rotate", each individual value is expressed as <rotate-angle> [<cx> <cy>]
You can specify the center for the rotation if you provide 2 additional values, cx and cy.
So, for your piece of code, I add "50 50" in the "from" and "to" attribute :
<rect fill="#FE9FFF" width="100px" height="100px">
<animateTransform attributeName="transform" type="rotate"
from="0 50 50" to="360 50 50" dur="20s" repeatDur="indefinite"/>
</rect>
It work with latest version of Opera, Safari and Chrome, also Firefrox 4 Beta and Batik.
To be clear: what we are trying to achieve is rotation around a center which is itself being translated. I find that if I have an <animateTransform type=rotate>, I cannot use <animateMotion>, nor <animateTransform type=translate> to perform simultaneous translation. It (latest Chrome or Firefox) does not interpolate the center of rotation as desired, resulting in a "loop de loop" instead. However, using a simple <animate> for each of the x,y coordinates does work as desired; in this case, <animateTransform type=rotate> interpolates the center along the x,y path, as long as you set the from= parameter to the start angle, start x and start y position, and set the to= parameter to the ending values.

Resources