Endless rotate of SVG Polygon on mouseover - javascript

I want to rotate a polygon on mouseover, but all I am able to do is a single rotate, I think because the angle is sort of static.
also, the polygon should rotate around itself, what it doesn't. My code looks like:
<polygon id="stern"
points="
350,370.5
370.9,460.1
460.9,460.1
390.7,510.5
420.3,600.1
350 ,550
270.7,600.1
300.3,510.5
230.1,460.1
320.1,460.1"
style="fill:#FACC2E"
onmouseover="rotieren()"/>
<use x="365" y="-380" xlink:href="#stern" transform="scale(0.7)"/>
<use x="1060" y="400" xlink:href="#stern" transform="scale(0.4)"/>
<use x="500" y="700" xlink:href="#stern" transform="scale(0.5)"/>
I tried many functions, for example to use a variable, but I can't use it in .setAttribute("transform", "rotate(variable,0,0)). Right now, I do
document.getElementByID("stern").setAttribut("transform","rotate(5,1060,400)")

Can you do something like this?
var stern = document.getElementByID("stern");
var i = 0;
var interval = setInterval(function(){
stern.setAttribute("transform","rotate("+(++i)+",1060,400)")
},50)

Related

How to draw Polyline object shaped exactly same with the given image data on SVG

Doughnut picture
For example, given image like above, what I want to do is draw the exact same shaped polyline object on SVG(Im creating a 'drawing' or should I say 'brush' tool based on SVG and that is why Im using polyline so that user can paint with his mouse or can even use eraser with his or hers mouse). And following is how I would achieve this.
draw the given image on canvas context.
get all the coordinates of pixel that is colored #000000.
with that list of coordinates create a Polyline on SVG.
and by this process I get this as a result doughnut drawin with svg polyline(now this is just an example result that it is formed ugly because I had to draw it manually with my hand. But my purpose is to get same shaped with an input image)
But I'm not sure if this is the only way or even not sure if I should stick with SVG. Are there any other good ways to achieve this? or would using canvas instead of SVG make it easier?
This shape can be drawn with circles.
Cutouts made using a mask composed of circles
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="405" height="401" viewBox="0 0 405 401" >
<defs>
<mask id="msk1" >
<rect width="100%" height="100%" fill="white" />
<g fill="black">
<circle cx="202" cy="200" r="40" />
<circle cx="260" cy="298" r="40" />
<circle cx="215" cy="303" r="20" />
</g>
</mask>
</defs>
<circle cx="202" cy="200" r="98" fill="black" mask="url(#msk1)" />
This is supposing that you already have an SVG path.
In order to draw a polygon you will need to split your path by the M commands. In the next example I did it manually but you can do it dynamically. This is important because otherwise you'll get a split in the polygon.
You will also need to set a precision, meaning the distance between the points of the polygon.
Please read the comments in my code.
let paths = document.querySelectorAll("#theGroup path");
let precision = 5;//how far one of other the points of the polygon are
let points = [];// the array of points
// for each path in the array of paths
paths.forEach(p=>{
// get the total length
let totalLength = p.getTotalLength();
// get the number of points for the polygon in base of the precision
let numPoints = ~~(totalLength / precision);
// calculate the segment length
let segmentLength = totalLength / numPoints;
for(let i = 0; i <= numPoints; i++ ){
let point = p.getPointAtLength(i*segmentLength);
// get the coordinates of this point and push it
points.push(point.x);
points.push(point.y);
}
})
//set the value for the points attribute of the polygon
poly.setAttributeNS(null,"points", points.join())
svg{border:1px solid; width:90vh;}
path{fill:none}
<svg viewBox="0 0 531 531">
<g id="theGroup">
<path id="donut" d="M268.64,76.066c70.065,2.632,125.154,32.347,163.73,91.372
c14.944,22.864,23.47,48.161,27.698,75.22c3.987,25.512,2.188,50.551-3.64,75.354c-4.821,20.522-13.383,39.648-24.866,57.406
c-2.003,3.099-3.899,3.396-7.365,1.548c-30.011-16.005-64.509-10.767-87.731,14.13c-6.295,6.748-9.985,15.893-15.108,23.783
c-1.548,2.384-3.508,5.256-5.938,6.189c-19.202,7.375-32.874,20.547-41.279,39.064c-1.911,4.211-4.254,5.562-8.308,5.085
c-13.198-1.554-26.507-2.515-39.562-4.873c-30.46-5.502-57.275-19.262-81.055-38.724c-28.703-23.491-49.496-52.646-61.424-88.046
c-7.479-22.198-11.34-44.892-10.42-68.225c2.042-51.761,20.944-96.305,57.854-133.023c22.272-22.156,48.427-37.859,78.3-47.183
C228.671,79.17,248.365,75.884,268.64,76.066z"/>
<path id="hole" d="M340.466,271.259c0.179-40.212-32.175-73.14-72.067-73.348
c-40.072-0.208-73.264,32.326-73.485,72.032c-0.226,40.441,32.218,73.372,72.436,73.522
C307.646,343.616,340.286,311.382,340.466,271.259z"/>
</g>
<polygon id="poly" fill="gold" points = "" />
</svg>

Can anyone spot where I'm going wrong with this SVG/Javascript function?

I have put together some SVG circles, with the colour of the circles supposed to alternate on each click between blue and red.
I'm getting some inconsistent behaviour from this, as when I click on the circles in my localhost environment, it works, but the colour doesn't change to red until the second click.
When I put the code into jsFiddle, one of the circles works, one doesn't, even though the code is the same, and they both give the same error in the console
[Error] ReferenceError: Can't find variable: changeColor1
onclick (_display:79)
var shapeClick = document.getElementById("circle0").addEventListener("click", changeColor);
var clicks = 0;
var colorToggle = true;
function changeColor() {
var color = colorToggle ? "#ff0000" : "#1dacf9";
circle0.setAttribute('fill', color);
colorToggle = !colorToggle;
}
var shapeClick = document.getElementById("circle1").addEventListener("click", changeColor1);
var clicks = 0;
var colorToggle = true;
function changeColor1() {
var color = colorToggle ? "#ff0000" : "#1dacf9";
circle1.setAttribute('fill', color);
colorToggle = !colorToggle;
}
<svg id="table1" width="66%" height="100vh" viewBox="0 0 700 666">
<circle id="circle0" class="circles" cx="170" cy="125" r="20" fill="#1dacf9" stroke="black" stroke-width="2" onclick="changeColor()"/>
<circle id="circle1" class="circles" cx="250" cy="45" r="20" fill="#1dacf9" stroke="black" stroke-width="2" onclick="changeColor1()"/>
</svg>
I'm sure there is a more streamline way to do this, especially if I scale it up and add 100 circles, I can't think of a way to do it without using a massive amount of code.
In your case, you are redefining variables that already exist, and both circles are referencing the same value for their toggle.
var shapeClick = ...
...
var shapeClick = ...
Also, you don't need to do onclick="function()" and addEventListener; they do the same thing.
As far as streamlining, there are a couple things you should do:
Create an object to hold toggle states. Keys should be IDs of the circles, and the values will be true or false to signify each circle's state.
Make one handler function, and use event.target.getAttribute('id') to identify which circle was clicked.
Should end up looking something like this:
var state = {};
var circleClickHandler = function (event) {
var id = event.target.getAttribute('id');
var color = state[id] ? "#ff0000" : "#1dacf9";
event.target.setAttribute('fill', color);
state[id] = !state[id];
};
// loop over each circle on the DOM
document.querySelectorAll('circle').forEach(function (element) {
element.addEventListener('click', circleClickHandler);
});
you are missing you opening svg tag
https://jsfiddle.net/9oagrcfu/
<svg>
<circle id="circle0" class="circles" cx="170" cy="125" r="20" fill="#1dacf9" stroke="black" stroke-width="2" onclick="changeColor()"/>
<circle id="circle1" class="circles" cx="250" cy="45" r="20" fill="#1dacf9" stroke="black" stroke-width="2" onclick="changeColor1()"/>
</svg>

How to zoom in on a polygon in D3.js

I am trying to figure out how to do the same zooming behavior as shown in the example below, but with a normal polygon instead of the geo paths.
https://bl.ocks.org/mbostock/4699541
I have seen some answers here on SO that kind of address this, but the animation is choppy or jumps around strangely.
The html I have is
<div id="map-container">
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
id="canvas"
viewBox="0 0 4328 2880">
<defs>
<pattern id="mapPattern"
patternUnits="userSpaceOnUse"
x="0"
y="0"
width="4328"
height="2880">
<image xlink:href="/development/data/masterplan.png"
x="0"
y="0"
width="4328"
height="2880"></image>
</pattern>
</defs>
<g id="masterGroup">
<rect fill="url(#mapPattern)"
x="0"
y="0"
width="4328"
height="2880" />
</g>
</svg>
I would like to be able to add some polygons in the same group as the map rectangle and then zoom on the polygon's boundary. Can anyone please show me a fiddle of such behaviour?
I should also add that I do not want to use the scroll wheel or panning. Just zooming in on a clicked polygon and then zooming out on another click.
Maybe this will help you. I answered a question here earlier today : D3js outer limits
Here is the fiddle I put together : http://jsfiddle.net/thatOneGuy/JnNwu/921/
I have added a transition : svg.transition().duration(1000).attr('transform',function(d){
Notice if you click one of the nodes the area moves to cater for the size of the new layout.
The basics are explained in the link to the question, but basically I got the bounding box and translated the SVG accordingly. So I translated and scaled to the size of the new rectangle.
Take a look, quite easy to understand. Here is the main part of the transition :
svg.transition().duration(1000).attr('transform',function(d){
var testScale = Math.max(rectAttr[0].width,rectAttr[0].height)
var widthScale = width/testScale
var heightScale = height/testScale
var scale = Math.max(widthScale,heightScale);
var transX = -(parseInt(d3.select('#invisRect').attr("x")) + parseInt(d3.select('#invisRect').attr("width"))/2) *scale + width/2;
var transY = -(parseInt(d3.select('#invisRect').attr("y")) + parseInt(d3.select('#invisRect').attr("height"))/2) *scale + height/2;
return 'translate(' + transX + ','+ transY + ')scale('+scale+')' ;
})
So with your code, your rectAttr values as seen in the snippet above would be the values retrieved from the getBoundingClientRect() of your polygon : x, y, width and height.
Where I have used d3.select('#invisRect'), this should be your boundingBoxRect() also. And the rest should just work.
EDIT
Here are the edits I made with the fiddle provided : http://jsfiddle.net/thatOneGuy/nzt39dym/3/
I used this function to get the bounding box of the polygon and set the rectangles values accordingly :
var bbox = d3.select(polygon).node().getBBox();
var rectAttr = {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
};

How to use the SVG checkintersection() function correctly?

I'm having a problem with the SVG checkintersection() function. All I want to do is to check whether a small SVG-rectangle intersects the area of an SVG-path, but I can't figure out what to call the function on (I already tried to call it on the SVG DOM object, among several other things google turned up).
So what I need to know is what to put in for the placeholder ("foo") in this snippet:
var closedPath = document.getElementById(closedPath);
var rectangle = document.getElementById(rectangle);
if (foo.checkIntersection(closedPath, rectangle)) {
//do stuff
};
with the HTML being something along the lines of
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svgroot">
<g>
<path id="closedPath" fill="{$c5}" d="M-250179-46928l-5051 1351l-867-1760l-33-146l-12-99l-82-678l-17-249l86-644l305-1800l158-2882l75-1425l-47-280l-22-131l-137-411l-300-892l1273 620l931-109l1957-734l1860-1096l292-192l884 547l2690 2153l480 963l36 244l-948 1878l-376 591l-60 567l-72 1147l97 847l-222 334l-122 117l-2403 2093l-353 76z"/>
<rect id="rectangle" fill="white" x="-126828" y="0" width="45000" height="45000"/>
</g>
</svg>
</body>
</html>
Any help would be much appreciated!
Edit: Just wanted to add that I now use a workaround, which consists of converting the SVG path to an array of point coordinates using a parser function I wrote, which is then put into a simple coordinate-test function.
Also this may have been a solution Hit-testing SVG shapes?
checkIntersection is a method on the <svg> element so you'd want something like this...
var svg = document.getElementById("svgroot");
var closedPath = document.getElementById(closedPath);
var rectangle = document.getElementById(rectangle);
var rect = svg.createSVGRect();
rect.x = rectangle.animVal.x;
rect.y = rectangle.animVal.y;
rect.height = rectangle.animVal.height;
rect.width = rectangle.animVal.width;
svg.checkIntersection(closedPath, rect) {
// do stuff
}
Note also how the second argument has to be an SVGRect and not an element.
SVG elements support SMIL animation, you could equally well write rectangle.baseVal.x etc but that wouldn't necessarily reflect the rectangle's current position if you were animating the rectangle. If you're not using SMIL then rectangle.baseVal.x = rectangle.animVal.x
Because a <rect> can have things like rounded corners it doesn't have an SVGRect interface so you have to convert from the interface it does have (SVGRectElement) to the one you need (SVGRect)
<svg width="390" height="248" viewBox="-266600, -68800, 195000, 124000" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path id="closedPath" fill="#ff9966" d="M-250179-46928l-5051 1351l-867-1760l-33-146l-12-99l-82-678l-17-249l86-644l305-1800l158-2882l75-1425l-47-280l-22-131l-137-411l-300-892l1273 620l931-109l1957-734l1860-1096l292-192l884 547l2690 2153l480 963l36 244l-948 1878l-376 591l-60 567l-72 1147l97 847l-222 334l-122 117l-2403 2093l-353 76z"/>
<rect id="rectangle" fill="#66ff66" x="-126828" y="0" width="45000" height="45000"/>
</svg>
<script>
var rectangle = document.getElementById('rectangle');
var closedPath = document.getElementById('closedPath');
var svgRoot = closedPath.farthestViewportElement;
var rect = svgRoot.createSVGRect();
rect.x = rectangle.x.animVal.value;
rect.y = rectangle.y.animVal.value;
rect.height = rectangle.height.animVal.value;
rect.width = rectangle.width.animVal.value;
var hasIntersection = svgRoot.checkIntersection(closedPath, rect);
console.log(hasIntersection);
</script>

How to draw non-scalable circle in SVG with Javascript

I'm developing a map, in Javascript using SVG to draw the lines.
I would like to add a feature where you can search for a road, and if the road is found, a circle appears on the map.
I know i can draw a circle in SVG, but my problem is that, the size of the circle should not change depending on the zoom-level. In other words the circle must have the same size at all times.
The roads on my map have this feature, all i had to do was add
vector-effect="non-scaling-stroke"
to the line attributes..
A line looks like this.
<line vector-effect="non-scaling-stroke" stroke-width="3" id = 'line1' x1 = '0' y1 = '0' x2 = '0' y2 = '0' style = 'stroke:rgb(255,215,0);'/>
The circle looks like this.
<circle id = "pointCircle" cx="0" cy="0" r="10" stroke="red" stroke-width="1" fill = "red"/>
Is it possible to define the circle as "non-scaling" somehow?
It took me a while, but I finally got the math clean. This solution requires three things:
Include this script in your page (along with the SVGPan.js script), e.g.
<script xlink:href="SVGPanUnscale.js"></script>
Identify the items you want not to scale (e.g. place them in a group with a special class or ID, or put a particular class on each element) and then tell the script how to find those items, e.g.
unscaleEach("g.non-scaling > *, circle.non-scaling");
Use transform="translate(…,…)" to place each element on the diagram, not cx="…" cy="…".
With just those steps, zooming and panning using SVGPan will not affect the scale (or rotation, or skew) of marked elements.
Demo: http://phrogz.net/svg/scale-independent-elements.svg
Library
// Copyright 2012 © Gavin Kistner, !#phrogz.net
// License: http://phrogz.net/JS/_ReuseLicense.txt
// Undo the scaling to selected elements inside an SVGPan viewport
function unscaleEach(selector){
if (!selector) selector = "g.non-scaling > *";
window.addEventListener('mousewheel', unzoom, false);
window.addEventListener('DOMMouseScroll', unzoom, false);
function unzoom(evt){
// getRoot is a global function exposed by SVGPan
var r = getRoot(evt.target.ownerDocument);
[].forEach.call(r.querySelectorAll(selector), unscale);
}
}
// Counteract all transforms applied above an element.
// Apply a translation to the element to have it remain at a local position
function unscale(el){
var svg = el.ownerSVGElement;
var xf = el.scaleIndependentXForm;
if (!xf){
// Keep a single transform matrix in the stack for fighting transformations
// Be sure to apply this transform after existing transforms (translate)
xf = el.scaleIndependentXForm = svg.createSVGTransform();
el.transform.baseVal.appendItem(xf);
}
var m = svg.getTransformToElement(el.parentNode);
m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
xf.setMatrix(m);
}
Demo Code
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Scale-Independent Elements</title>
<style>
polyline { fill:none; stroke:#000; vector-effect:non-scaling-stroke; }
circle, polygon { fill:#ff9; stroke:#f00; opacity:0.5 }
</style>
<g id="viewport" transform="translate(500,300)">
<polyline points="-100,-50 50,75 100,50" />
<g class="non-scaling">
<circle transform="translate(-100,-50)" r="10" />
<polygon transform="translate(100,50)" points="0,-10 10,0 0,10 -10,0" />
</g>
<circle class="non-scaling" transform="translate(50,75)" r="10" />
</g>
<script xlink:href="SVGPan.js"></script>
<script xlink:href="SVGPanUnscale.js"></script>
<script>
unscaleEach("g.non-scaling > *, circle.non-scaling");
</script>
</svg>
If you are looking for a fully static way of doing this, you might be able to combine non-scaling-stroke with markers to get this, since the markers can be relative to the stroke-width.
In other words, you could wrap the circles in a <marker> element and then use those markers where you need them.
<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000">
<marker id="Triangle"
viewBox="0 0 10 10" refX="0" refY="5"
markerUnits="strokeWidth"
markerWidth="4" markerHeight="3"
orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
<path d="M 100 100 l 200 0" vector-effect="non-scaling-stroke"
fill="none" stroke="black" stroke-width="10"
marker-end="url(#Triangle)" />
<path d="M 100 200 l 200 0"
fill="none" stroke="black" stroke-width="10"
marker-end="url(#Triangle)" />
</svg>
The same can also be viewed and tweaked here. The svg spec isn't fully explicit about what should happen in this case (since markers are not in SVG Tiny 1.2, and vector-effect isn't in SVG 1.1). My current line of thinking was that it should probably affect the size of the marker, but it seems no viewers do that at the moment (try in a viewer that supports vector-effect, e.g Opera or Chrome).
Looks like some work was done in webkit (maybe related to this bug: 320635) and the new transform doesn't stick around when simply appended like that
transform.baseVal.appendItem
This seems to work better. Even works in IE 10.
EDIT: Fixed the code for more general case of multiple translate transformations in the front and possible other transformations after. First matrix transformation after all translates must be reserved for unscale though.
translate(1718.07 839.711) translate(0 0) matrix(0.287175 0 0 0.287175 0 0) rotate(45 100 100)
function unscale()
{
var xf = this.ownerSVGElement.createSVGTransform();
var m = this.ownerSVGElement.getTransformToElement(this.parentNode);
m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
xf.setMatrix(m);
// Keep a single transform matrix in the stack for fighting transformations
// Be sure to apply this transform after existing transforms (translate)
var SVG_TRANSFORM_MATRIX = 1;
var SVG_TRANSFORM_TRANSLATE = 2;
var baseVal = this.transform.baseVal;
if(baseVal.numberOfItems == 0)
baseVal.appendItem(xf);
else
{
for(var i = 0; i < baseVal.numberOfItems; ++i)
{
if(baseVal.getItem(i).type == SVG_TRANSFORM_TRANSLATE && i == baseVal.numberOfItems - 1)
{
baseVal.appendItem(xf);
}
if(baseVal.getItem(i).type != SVG_TRANSFORM_TRANSLATE)
{
if(baseVal.getItem(i).type == SVG_TRANSFORM_MATRIX)
baseVal.replaceItem(xf, i);
else
baseVal.insertItemBefore(xf, i);
break;
}
}
}
}
EDIT2:
Chrome killed getTransformToElement for some reason, so the matrix needs to be retrieved manually:
var m = this.parentNode.getScreenCTM().inverse().multiply(this.ownerSVGElement.getScreenCTM());
It's discussed here and here
It looks like current browsers don't do the expected thing, so one needs to apply the inverse transform of the zoom (scale) on the contents of the <marker>, eg. transorm: scaleX(5) on the user of the <marker> etc. will need to be accompanied by a transform: translate(...) scaleX(0.2) inside the <pattern>, also factoring in possible x/y/width/height/transform-origin values inside the pattern if needed

Categories

Resources