I have an svg file that has all the countries in the world. Each country has their own individual paths for example:
<path
inkscape:connector-curvature="0"
id="PH"
data-name="Philippines"
data-id="PH"
d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z"
style="fill:#1abc9c;fill-rule:evenodd;" />
<path
inkscape:connector-curvature="0"
id="PL"
data-name="Poland"
data-id="PL"
d="m 1069.4,228.3 -4.6,-0.1 -0.5,-1.4 -4.8,-1.1 -5.7,2.1 -7.1,2.8 -3.1,1.7 1.4,3.1 -1.2,1.6 2,2.2 1.4,3.3 -0.1,2.1 2.3,3.9 2.4,1.9 3.7,0.6 -0.1,1.7 2.7,1.2 0.6,-1.5 3.4,0.6 0.7,2 3.6,0.3 2.6,3.1 0.3,0.4 1.9,-0.9 2.7,2.2 2.8,-1.3 2.4,0.6 3.4,-0.8 4.9,2.3 1.1,0.4 -1.6,-2.8 3.8,-5.1 2.3,-0.7 0.3,-1.8 -3.1,-5.3 -0.5,-2.7 -1.9,-2.9 2.7,-1.2 -0.3,-2.4 -1.7,-2.3 -0.6,-2.7 -1.4,-1.9 -2.5,-0.6 -8.7,0.1 -5.9,-0.7 z"
style="fill:#f2f2f2;fill-rule:evenodd" />
Now I want to access each country through a function in JS. So I added a href before the country:
<a href = "#" onclick = "SelectCountry('ph');" id = 'country_ph'>
<path
inkscape:connector-curvature="0"
id="PH"
data-name="Philippines"
data-id="PH"
d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z"
style="fill:#1abc9c;fill-rule:evenodd;" />
</a>
and access them here:
var phCountry = document.getElementById("PH");
function SelectCountry(country)
{
alert(country);
}
However I want to scale the country the user clicks on. I tried
phCountry.setAttribute("height", "100px");
phCountry.style.height = "100px";
I'm very new to svg and js so I may probably be doing this in the wrong way.
How can I fix this? Thanks a ton!
<path> elements do not have a width or height attribute you can change. What you have to do is apply a transform that scales the country paths when you click on them.
Also, you don't need to add <a> tags to your SVG. Just add a click handler to each path.
See the example below. I've described how it works in the code comments.
// Look for all <path> elements in the SVG and add a click handler to each one
document.querySelectorAll("svg path").forEach(function(item) {
item.addEventListener("click", countryClick);
});
function countryClick(e) {
// e.target is the path we clicked on.
// classList.toggle("enlarge") will cause the "enlarge" class to be added then removed on alternate clicks
e.target.classList.toggle("enlarge");
}
.enlarge {
/* Use the bounds of the path to calculate the transform origin */
transform-box: fill-box;
/* Set the origin for scaling to the centre of the path bounds */
transform-origin: 50% 50%;
/* Scale up by 2 in both the x and y directions */
transform: scale(2, 2);
}
<svg viewBox="1000 200 800 400">
<path
inkscape:connector-curvature="0"
id="PH"
data-name="Philippines"
data-id="PH"
d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z"
style="fill:#1abc9c;fill-rule:evenodd;" />
<path
inkscape:connector-curvature="0"
id="PL"
data-name="Poland"
data-id="PL"
d="m 1069.4,228.3 -4.6,-0.1 -0.5,-1.4 -4.8,-1.1 -5.7,2.1 -7.1,2.8 -3.1,1.7 1.4,3.1 -1.2,1.6 2,2.2 1.4,3.3 -0.1,2.1 2.3,3.9 2.4,1.9 3.7,0.6 -0.1,1.7 2.7,1.2 0.6,-1.5 3.4,0.6 0.7,2 3.6,0.3 2.6,3.1 0.3,0.4 1.9,-0.9 2.7,2.2 2.8,-1.3 2.4,0.6 3.4,-0.8 4.9,2.3 1.1,0.4 -1.6,-2.8 3.8,-5.1 2.3,-0.7 0.3,-1.8 -3.1,-5.3 -0.5,-2.7 -1.9,-2.9 2.7,-1.2 -0.3,-2.4 -1.7,-2.3 -0.6,-2.7 -1.4,-1.9 -2.5,-0.6 -8.7,0.1 -5.9,-0.7 z"
style="fill:#f2f2f2;fill-rule:evenodd" />
</svg>
However there is one extra step you'll inevitably need to do. If you clicked on the square I have added in the first example, then you will have discovered it already. Elements in an SVG have a draw order. Earlier paths in the file are drawn before later ones. So you will find when you enlarge one country, it may be behind another un-enlarged country.
That's probably not what you want. So to fix that, you also need to move the country you are enlarging to the front. You do that by moving it to the end of the SVG.
In the next example, I've added the code to do that.
// Look for all <path> elements in the SVG and add a click handler to each one
document.querySelectorAll("svg path").forEach(function(item) {
item.addEventListener("click", countryClick);
});
function countryClick(e) {
// e.target is the path we clicked on.
// classList.toggle("enlarge") will cause the "enlarge" class to be added then removed on alternate clicks
e.target.classList.toggle("enlarge");
// If we are enlarging, then also move the path to the end of the SVG
if (e.target.classList.contains("enlarge")) {
var svg = e.target.ownerSVGElement;
// appendChild() adds an element to the end of the SVG.
// If it is already in the SVG, it gets moved from it's current position in the order.
svg.appendChild(e.target);
}
}
.enlarge {
/* Use the bounds of the path to calculate the transform origin */
transform-box: fill-box;
/* Set the origin for scaling to the centre of the path bounds */
transform-origin: 50% 50%;
/* Scale up by 2 in both the x and y directions */
transform: scale(2, 2);
}
<svg viewBox="1000 200 800 400">
<path
inkscape:connector-curvature="0"
id="PH"
data-name="Philippines"
data-id="PH"
d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z"
style="fill:#1abc9c;fill-rule:evenodd;" />
<path
inkscape:connector-curvature="0"
id="PL"
data-name="Poland"
data-id="PL"
d="m 1069.4,228.3 -4.6,-0.1 -0.5,-1.4 -4.8,-1.1 -5.7,2.1 -7.1,2.8 -3.1,1.7 1.4,3.1 -1.2,1.6 2,2.2 1.4,3.3 -0.1,2.1 2.3,3.9 2.4,1.9 3.7,0.6 -0.1,1.7 2.7,1.2 0.6,-1.5 3.4,0.6 0.7,2 3.6,0.3 2.6,3.1 0.3,0.4 1.9,-0.9 2.7,2.2 2.8,-1.3 2.4,0.6 3.4,-0.8 4.9,2.3 1.1,0.4 -1.6,-2.8 3.8,-5.1 2.3,-0.7 0.3,-1.8 -3.1,-5.3 -0.5,-2.7 -1.9,-2.9 2.7,-1.2 -0.3,-2.4 -1.7,-2.3 -0.6,-2.7 -1.4,-1.9 -2.5,-0.6 -8.7,0.1 -5.9,-0.7 z"
style="fill:#f2f2f2;fill-rule:evenodd" />
<path d="M 1100,228 h 30 v 30 h -30 z" fill="#ccaa88"/>
</svg>
Related
I have this svg .
<svg>
<path xmlns="http://www.w3.org/2000/svg" fill="#012d26" d=" M 81.16 38.23 C 83.16 42.68 85.01 47.20 87.05 51.63 C 89.59 57.31 91.99 63.04 94.50 68.73 C 107.87 65.61 122.71 76.15 123.25 90.07 C 124.68 103.15 112.95 115.43 99.93 115.20 C 87.82 115.97 76.31 106.03 75.34 93.93 C 74.34 84.23 80.19 74.37 89.14 70.51 C 87.75 64.47 86.08 58.49 84.69 52.45 C 83.60 47.69 82.33 42.97 81.16 38.23 Z"/>
</svg>
and i want to rotate it on its axis. Like a meter needle.
I tried ..
transform: rotateZ(0deg);
but, this is rotating the entire image. I want to keep the base fixed and only rotate the needle part.
This can be done by :
transform-origin:bottom;
transform: rotateZ(0deg);
I have a SVG Pattern which I use as fill in many other SVGs.
Of course, when I change attributes in the pattern afterwards, it is changed in every SVG using this pattern.
Is there a possibility to modify the pattern attributes in a specific instance/use only?
I would like to avoid creating the pattern in 100 different versions before.
EDIT with sample:
This is a sample pattern. I use this icon as fill for a circle on a country on a world map. The circle inside the pattern is used to set the background color.
<svg width="0" height="0">
<defs>
<pattern id="infantry_svg" patternUnits="objectBoundingBox" width="100% " height="100%">
<circle r="10" fill="transparent"></circle>
<path d="M 19.328,3.097 19.025,2.633 18.35,3.045 17.638,2.148 17.555,3.563 15.188,5.011 C 14.818,4.596 14.555,4.484 14.555,4.484 L 10.343,7.079 10.039,6.959 9.871,6.7 13.072,4.919 C 12.811,4.496 12.29,3.65 12.29,3.65 L 9.295,5.893 9.173,5.968 9.028,5.732 8.342,6.155 8.488,6.393 8.276,6.523 7.838,5.811 7.152,6.234 7.59,6.946 7.293,7.129 7.146,6.891 6.46,7.314 6.606,7.551 6.584,7.566 3.881,8.993 4.556,10.089 7.139,8.339 C 7.26,8.536 7.353,8.687 7.353,8.687 l -1.748,1.074 0.293,0.456 -0.373,1.185 -1.518,1.412 c 0,0 -2.772,1.469 -4.007,2.152 0.308,0.691 0.964,1.57 1.443,2.214 0,0 4.855,-4.879 6.706,-4.765 l 1.837,-1.136 c 0,0 -0.752,-0.704 -0.5,-0.864 1.583,1.752 3.062,1.704 3.062,1.704 0.442,-0.568 0.528,-1.221 0.78,-1.834 -1.195,-0.286 -1.226,-0.103 -2.25,-1.111 l 4.673,-2.88 C 15.821,6.048 15.48,5.467 15.48,5.467 l 3.848,-2.37 z m -9.83,8.165 -1.203,0.742 c 0,0 -0.281,-0.437 -0.435,-0.688 l 0.265,-0.16 c 0.168,0.165 0.502,0.417 0.834,0.213 l -0.124,-0.2 C 8.662,11.275 8.461,11.146 8.333,11.03 l 0.743,-0.453 0.422,0.685 z"
fill="#030104" transform="translate(1,1.1) scale(0.29)" stroke="white" stroke-width="0.4" />
</pattern>
</defs>
</svg>
Now when I set this pattern inside a circle on a country, it looks like this:
The circle on the country is generated like this with Snap.svg:
var circle = xs.circle(x, y, 4).attr({ id: "blabla", fill: "url(#infantry_svg)", stroke: "black", strokeWidth: 1 }).appendTo(xs.select('g'));
Directly after this, I set the background color of the pattern used for this circle:
$("#infantry_svg circle").attr({fill: troopOwner_color]});
Now every circle using "infantry_svg" pattern, has "troopOwner_color" as background color of the circle.
But I would like to change only this single instance of the pattern usage.
Whilst you can't do what you want with the specific requirement of the question, ie using a pattern. I'm not sure why you need to use a pattern.
Why not just use a 'use' element for the defs statement, and have a path & circle in there. Then you can just clone it and change the fill...
<defs>
<g id="infantry_svg" >
<circle cx="30" cy="30" r="40" stroke="red" stroke-width="5"/>
<path>path stuff</path>
</defs>
var g = s.g().use( Snap.select('#infantry_svg') );
var g1 = s.g( g ).attr({ fill: 'yellow' });
var g2 = g.clone().attr({ fill: 'blue', transform: 't200,0' })
var g2 = g.clone().attr({ fill: 'green', transform: 't100,0' })
jsfiddle
Just include the bits that don't change in the defs element, and create the bits that will be unique.
I'm trying to animate a flower SVG image so that it will look like the flower is drawing itself, as described in this article. I've included my current code in JSFiddle here.
I can't seem to access each path element, and I'm not too sure why. I used the Javascript code provided in the article, and I downloaded/included the svg HTML for an SVG flower I found online. When I run the code, the console in Firefox, Safari, and Chrome logs 0, telling me that there are no elements with the tag name path. I'm not sure why this is; can anyone explain why/what I should fix?
Here is the javascript code:
var paths = document.getElementsByTagName('path');
console.log(paths.length);
for (var i = 0; i < paths.length; i++) {
var path = paths[i];
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition =
'none';
// Set up the starting positions
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 2s ease-in-out';
// Go!
path.style.strokeDashoffset = '0';
}
Here is some of my HTML code:
<body>
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="800px"
height="800px"
id="svg2385">
<!-- <defs
id="defs2387" />
<g
transform="translate(-163.06366,-372.39925)"
id="layer1"> -->
<path
d="M 368.89962,527.3412 C 359.4568,464.38904 307.36168,439.5961 286.4323,430.39488 C 267.82968,422.21658 263.76952,407.10258 263.76952,407.10258 C 263.76952,407.10258 258.73335,424.09966 258.73335,432.28344 C 258.73335,440.46722 256.84479,478.86804 271.9533,510.97363 C 287.06182,543.07923 333.01689,554.41062 346.23685,570.14866 C 359.4568,585.8867 354.42063,613.58565 354.42063,613.58565 C 354.42063,613.58565 353.79111,591.55239 338.68259,578.33244 C 323.57407,565.11249 286.4323,565.11249 265.65809,527.3412 C 244.88388,489.5699 243.11868,419.10884 261.25144,392.62358 C 270.14221,379.63744 261.94361,399.48194 275.73043,412.13875 C 288.52321,423.88299 327.25144,435.53708 350.01398,458.72335 C 375.97069,485.16325 369.52915,528.60024 368.89962,527.3412 z"
id="path2395"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="M 339.98723,553.05793 C 345.42186,528.07876 345.25711,516.73109 330.15128,500.99047 C 314.74039,484.93195 299.71749,490.29179 282.61153,461.86813 C 291.50944,496.40407 313.58336,493.78902 325.88728,512.78659 C 337.91025,531.35036 338.75504,552.37736 339.98723,553.05793 z"
id="path3199"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
Solved this. Because my Javascript code was loading before my HTML code, it was registering 0 paths. I put the script tag between and and it worked.
I have closed SVG path that is the province of the country.
How to recognize point(x,y) is inside SVG path or outside by javascript?
For SVGGeometryElement, which includes paths and the basic shapes, there are
SVGGeometryElement.isPointInStroke() and
SVGGeometryElement.isPointInFill().
They return whether the given point is in the stroke respective in the fill, just as the name suggests.
Example:
const svg = document.getElementById("your-svg");
const path = document.getElementById("your-path");
// SVGPoint is deprecated according to MDN
let point = svg.createSVGPoint();
point.x = 40;
point.y = 32;
// or according to MDN
// let point = new DOMPoint(40, 32);
console.log("In stroke:", path.isPointInStroke(point)); // shows true
console.log("In fill:", path.isPointInFill(point)); // shows false
<svg id="your-svg" width="200" height="200">
<path d="M 10 80 C 40 10, 65 10, 95 80 S 150 10, 150 110 S 80 190, 40 140 Z" stroke="yellowgreen" stroke-width="5" fill="#adff2f77" id="your-path"/>
<!-- only to show where the point is -->
<circle id="stroke-point" cx="40" cy="32" r="2.5" fill="red" />
</svg>
Besides being more descriptive than Document.elementFromPoint() those functions handle stacked elements and pointer events correctly. Note that the above example already contains the small circle laying over the path at the requested point. It is not or only hardly possible to check this case with Document.elementFromPoint().
const svg = document.getElementById("your-svg");
const path = document.getElementById("your-path");
console.log("In stroke / fill:", svg.ownerDocument.elementFromPoint(40, 32) == path);
<svg id="your-svg" width="200" height="200">
<path d="M 10 80 C 40 10, 65 10, 95 80 S 150 10, 150 110 S 80 190, 40 140 Z" stroke="yellowgreen" stroke-width="5" fill="#adff2f77" id="your-path"/>
<!-- only to show where the point is -->
<circle id="stroke-point" cx="40" cy="32" r="2.5" fill="red" />
</svg>
Edit: Thanks to #Arlo who pointed out that the point representation object to use is not clear. MDN is using a DOMPoint (or DOMPointInit). Chrome assumed to get an SVGPoint which is deprecated according to MDN.
Note that the support on Edge and1 Internet Explorer is unknown at the moment (according to MDN).
1According to MDN Edge ≥79 supports both, isPointInStroke() and isPointInFill().
Call document.elementFromPoint. If the position is in the path then it will return that element. If the path is not filled then you may need to adjust the pointer-events property so that it works properly.
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