How do I rotate and skew an SVG rect "in-place"? - javascript

I am playing with SVG and I am stumped by something.
I am trying to make the pink square into a diamond by using skew and rotate.
However I am getting strange behaviour that I cannot figure out how to overcome.
Adding the skew, gets me the diamond effect I want, but then I need to rotate and reposition it so it lines up with the circles.
<rect x="126" y="0" width="40" height="40"fill="pink" transform="skewY(10)" />
However, when I apply rotation transform="rotate(45)" to the rect, it doesn't rotate "in-place", but rotates [I think] relative from the corner of the page.
<rect x="126" y="0" width="40" height="40"fill="pink" transform="skewY(10)" />
Does anyone know how I can freely rotate and skew this rectangle (in SVG and not CSS or anything) without it moving around so wildly and awkwardly ?
<h1>Shapes</h1>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="20" fill="blue" stroke="red" ></circle>
<circle cx="62" cy="20" r="20" fill="yellow" stroke="red" ></circle>
<circle cx="104" cy="20" r="20" fill="blue" stroke="red" ></circle>
<rect x="126" y="0" width="40" height="40"fill="pink"/>
<circle cx="188" cy="20" r="20" fill="green" stroke="red" ></circle>
</svg>

Simplest is to use transform-origin and transform-box
rect {
transform-origin: center;
transform-box: fill-box;
}
<h1>Shapes</h1>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="20" fill="blue" stroke="red" ></circle>
<circle cx="62" cy="20" r="20" fill="yellow" stroke="red" ></circle>
<circle cx="104" cy="20" r="20" fill="blue" stroke="red" ></circle>
<rect transform="rotate(45)" x="126" y="0" width="40" height="40"fill="pink"/>
<circle cx="188" cy="20" r="20" fill="green" stroke="red" ></circle>
</svg>

<h1>Shapes</h1>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="21" cy="21" r="20" fill="blue" stroke="red"></circle>
<circle cx="63" cy="21" r="20" fill="yellow" stroke="red"></circle>
<circle cx="105" cy="21" r="20" fill="blue" stroke="red"></circle>
<rect x="154" y="0" width="40" height="40" fill="pink" transform="rotate(45, 154, 0)"/>
<circle cx="203" cy="21" r="20" fill="green" stroke="red"></circle>
</svg>
This can be done using rotate. For rotate
First argument is the angle of rotation i.e. 45 degrees.
Second argument is the x offset about which the rect is to be rotated and is calculated as follows:
x = (border_width_of_circle_1 * 2 + radius_of_circle_1 * 2) + (border_width_of_circle_2 * 2 + radius_of_circle_2 * 2) + (border_width_of_circle_3 * 2 + radius_of_circle_3 * 2) + 1/2 * diagonal_of_square
= (2 + 40) + (2 + 40) + (2 + 40) + (1/2 * sqrt(40^2 + 40^2))
= 42 + 42 + 42 + (1/2 * sqrt(3200))
= (42 * 3) + (1/2 * 56)
= 126 + 28 = 154
Third argument is the y offset about which the rect is to be rotated, which in our case will be 0.

Related

Scale overriding SVG width and height attributes

I have an SVG element with a defined width and height, like <svg width="100px" height="100px"></svg>, filled with various elements.
I want to have a kind of "zoom" feature, where a particular region of the SVG is zoomed in on to fill the whole SVG element.
I planned to do this with the scale and translate attributes, i.e. by applying scale(x) to the SVG element and then calculating what I need to translate by in order to have the desired region remain visible.
I expected this would keep the SVG at 100x100px and simply hide any element outside this region. However, this doesn't happen; the whole SVG element just gets bigger instead, even though the dimensions are explicitly defined as attributes.
Clearly I'm misunderstanding the way that scaling and SVG dimensions work, does anyone know how I can achieve what I'm trying to do here?
You can warp the svg with a div element and use overflow: hidden.
<div style="width: 300px; height: 300px; overflow: hidden">
<svg width="100" height="100" style="transform: scale(4);">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
</svg>
</div>
Do you mean something like this?
function setViewBox(vbx){
svg.setAttribute("viewBox",vbx)
}
<svg viewBox="0 0 100 100" width="200px" height="200px" id="svg">
<rect x="0" y="0" width="100" height="100" stroke="black" fill="white" onclick="setViewBox('0 0 100 100')"/>
<circle cx="25" cy="25" r="25" fill="red" onclick="setViewBox('0 0 50 50')"/>
<rect x="60" y="10" width="30" height="30" fill="green" onclick="setViewBox('50 0 50 50')"/>
<rect x="10" y="60" width="30" height="30" fill="blue" transform="rotate(45,25,75)" onclick="setViewBox('0 50 50 50')"/>
<path d="M50 100L75 50L100 100z" fill="yellow" onclick="setViewBox('50 50 50 50')"/>
</svg>
or more like that?
var last=null
function setTransform(evt,trs){
reset()
svg.appendChild(evt.target)
evt.target.setAttribute("transform","scale(2 2) translate("+trs+")")
last=evt.target
}
function reset(){
if(last) last.removeAttribute("transform")
}
<svg viewBox="0 0 100 100" width="200px" height="200px" id="svg">
<rect x="0" y="0" width="100" height="100" stroke="black" fill="white" onclick="reset()"/>
<circle cx="25" cy="25" r="25" fill="red" onclick="setTransform(event,'0 0')"/>
<rect x="60" y="10" width="30" height="30" fill="green" onclick="setTransform(event,'-50 0')"/>
<rect x="10" y="60" width="30" height="30" fill="blue" transform="rotate(45,25,75)" onclick="setTransform(event,'0 -50')"/>
<path d="M50 100L75 50L100 100z" fill="yellow" onclick="setTransform(event,'-50 -50')"/>
</svg>

Create a polygon in SVG from a group of elements in JS

I have an SVG with groups of elements (which are all a uniform shape but the group shape can vary. Obviously putting an outline on the group will give me a rectangle shape. What I am trying to achieve is a polygon that outlines the group giving a rough trace of the items:
http://codepen.io/wroughtec/pen/OXvRrq
(below is an example with circles although have also included a path example as that is what I am actually being given)
<h2>No Outline</h2>
<svg viewbox="0 0 1000 200">
<g>
<circle cx="20" cy="20" r="10" fill="grey" />
<circle cx="50" cy="20" r="10" fill="grey" />
<circle cx="50" cy="50" r="10" fill="grey" />
<circle cx="80" cy="50" r="10" fill="grey" />
<circle cx="110" cy="50" r="10" fill="grey" />
<circle cx="140" cy="50" r="10" fill="grey" />
<circle cx="50" cy="80" r="10" fill="grey" />
<circle cx="80" cy="80" r="10" fill="grey" />
<circle cx="110" cy="80" r="10" fill="grey" />
<circle cx="140" cy="80" r="10" fill="grey" />
<circle cx="170" cy="80" r="10" fill="grey" />
<circle cx="200" cy="80" r="10" fill="grey" />
<circle cx="170" cy="110" r="10" fill="grey" />
<circle cx="200" cy="110" r="10" fill="grey" />
</g>
</svg>
I need to be able to do this automatically in JS but getting the x and y of the circles gives me the center point so my manual one I have had to cheat to expand to the outer shape of the circles and remove unnecessary points (i.e. points on the same line or in the middle).
It sounds like what you're looking for is the convex hull of the points.
D3.js implements this, and a good example can be found here. (The code is rather lengthy, so I won't reproduce it here; the main function of interest is d3.geom.hull)
Note that D3.js v4 was just recently released, and almost all code online (including what I linked) is written for v3. There are significant changes between the two, but v4 still has a convex hull function.

Scaling after rotation changes the position of rect element

Original rect, with a rotation:
<rect id="location_1" x="40" y="40" height="100" width="100" fill="red" stroke="2" stroke-width="2" transform="rotate(45, 90, 90)"></rect>
After scaling, the rotation center is changed based on the new bbox:
<rect id="location_1" x="40" y="40" height="200" width="200" fill="red" stroke="2" stroke-width="2" transform="rotate(45, 140, 140)"></rect>
After scaling of the rect, if we update the rotation center then the postion of the rect is changed. But if we do not update the center then it works as expected, till the other transformations are applied.
Scale as in updating width and height, not the scale transform
My question is how to scale keeping the position of the element same.?
JSFiddle with the situation
If clicked on Scale - No Update the position is not changed but when clicked on Scale - Update the position changes.
function scaleAndUpdate(id, update) {
var
elem = document.getElementById(id),
bbox, cx, cy;
elem.setAttribute('width', 200);
elem.setAttribute('height', 200);
if (update) {
bbox = elem.getBBox();
cx = bbox.x + (bbox.width / 2);
cy = bbox.y + (bbox.height / 2);
elem.setAttribute('transform', 'rotate(45, ' + cx + ', ' + cy + ')');
}
}
function reset(id) {
var
elem = document.getElementById(id);
elem.setAttribute('x', 40);
elem.setAttribute('y', 40);
elem.setAttribute('width', 100);
elem.setAttribute('height', 100);
elem.setAttribute('transform', 'rotate(45, 90, 90)');
}
<div>
<svg x="0" y="0" width="300" height="300" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<pattern id="smallGrid" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="gray" stroke-width="0.5" />
</pattern>
<pattern id="grid" width="100" height="100" patternUnits="userSpaceOnUse">
<rect width="100" height="100" fill="url(#smallGrid)" />
<path d="M 100 0 L 0 0 0 100" fill="none" stroke="gray" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" style="pointer-events: none;"></rect>
<g id="locationGroup">
<rect id="location_1" x="40" y="40" height="100" width="100" fill="red" stroke="2" stroke-width="2" transform="rotate(45, 90, 90)"></rect>
<circle cx="90" cy="20" r="5" fill="black"></circle>
</g>
</svg>
<button onclick="scaleAndUpdate('location_1')">Scale - No Update</button>
<button onclick="scaleAndUpdate('location_1', true)">Scale - Update</button>
<button onclick="reset('location_1')">Reset</button>
</div>

Change an inline SVG's x and y with ecmascript

I am using an inline SVG in an SVG and have set some default x and y values. When I change them, the inline SVG moves accordingly. I am trying to change it with
var inlineSVG = document.getElementById("inlineSVG");
inlineSVG.style.x = "90";
and that adds style="x:90px;" but that doesn't actually affect the element.
It's weird (in my head) because this works with a rect but not with an svg.
Here is my actual code:
<?xml version='1.0' encoding='UTF-8'?>
<svg width='1000' height='360'
xmlns='http://www.w3.org/2000/svg'
xmlns:xlink="http://www.w3.org/1999/xlink"
onload='init(evt)'
>
<script type='text/ecmascript'>
function init(event){
var wing1 = document.getElementById("wing1");
wing1.style.x = "90";
}
</script>
<circle cx="200" cy="140" r="5" fill="red" />
<circle cx="220" cy="170" r="5" fill="red" />
<circle cx="180" cy="170" r="5" fill="red" />
<circle cx="220" cy="220" r="5" fill="red" />
<circle cx="180" cy="220" r="5" fill="red" />
<svg id="wing1" x="280" y="100" viewBox="0 0 350 300">
<g>
<g>
<g>
<ellipse fill="#E6E7E8" cx="229.505" cy="117.813" rx="5.862" ry="4.547"/>
</g>
<g>
<ellipse fill="#E6E7E8" cx="265.931" cy="117.819" rx="5.862" ry="4.547"/>
</g>
</g>
<g>
<g>
<ellipse fill="#E6E7E8" cx="229.191" cy="125.538" rx="5.862" ry="4.547"/>
</g>
<g>
<ellipse fill="#E6E7E8" cx="265.617" cy="125.531" rx="5.861" ry="4.547"/>
</g>
</g>
</g>
<ellipse fill="#E6E7E8" cx="247.244" cy="121.796" rx="20.635" ry="38.017"/>
</svg>
<rect id="square" x="0" y="470" width="50" height="50" fill="#BADA55" style="fill-opacity : 0.5" />
<line x1="0" y1="0" x2="1000" y2="360" style="stroke: yellowgreen;
stroke-width: 1;
stroke-dasharray: 10 1;"></line>
<line x1="0" y1="360" x2="1000" y2="0" style="stroke: yellowgreen;
stroke-width: 1;
stroke-dasharray: 10 1;"></line>
I tried adding !important to the value but it didn't work ( because I guess it doesn't count it as a valid number? ).
The solution is to directly change the x attribute like so:
selector.setAttribute("attr",val);

in svg: translate vs position x and y

When I want to position simple objects such as rect or line should I use transform attribute or x and y attributes?
// this
d3.selectAll('rect')
.attr('x', d => d)
.attr('y', 0)
// or this?
d3.selectAll('rect')
.attr("transform", d => `translate(${d}, 0)`);
What is the performance difference?
In SVG transform is not hardware accelerated. They have around the same performance for single elements (in my experience). However, I use transform more to move thing around because in SVG not all elements have a x or y attributes, consider...
<line x1="0" y1="0" x2="100" y2="100" />
<circle cx="100" cy="100" r="100" />
<path d="M 0 0 L 100 100" />
<rect x="0" y="0" width="100" height="100" />
You must write a different implementation for each of these elements if you are not using transform. One area where transform is indeed faster is moving a large number of elements, if you have...
<g transform="translate(100, 100)">
<line x1="0" y1="0" x2="100" y2="100" />
<circle cx="100" cy="100" r="100" />
<path d="M 0 0 L 100 100" />
<rect x="0" y="0" width="100" height="100" />
</g>
It will be less processing intensive than moving each element individually

Categories

Resources