SVG HTML5 change pattern attributes in specific instance/usage only - javascript

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.

Related

Adjust height of SVG within JS/CSS

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>

jquery Select value attributes and apply each value

I try to select all stroke attributes and return their value for each corresponding element. Only, I used each (), map () I can not do it.
I have a function
function (el, i) {
i = $('.line-code path').attr('stroke');
return i
}
With this one, I select only applies the first color to all elements and that is not what I want.
Here are the elements that I want to select (there are 33 in total, but I put only 3)
<path d="M720.5 521.5H785" stroke="#4990E2"/>
<path d="M757.5 541.5H787" stroke="#000"/>
<path d="M808.5 541.5H911" stroke="#FFF"/>
And then, with my function, I want to apply the color retrieved to each corresponding element.
PS: Higher in my script, I define all colors to "none", thanks to this function, I want to reapply the original colors.
Thank you very much !
you can use array#map to get all the values.
var arr=[];
//iterating the path
$('path').map(function(v){
//push values of stroke into the array
arr.push($(this).attr('stroke'));
});
$('p').map(function(i,v){
//for each p assign a color from the array
$(this).css("color",arr[i])
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<path d="M720.5 521.5H785" stroke="#4990E2"/>
<path d="M757.5 541.5H787" stroke="#000"/>
<path d="M808.5 541.5H911" stroke="#eee"/>
<p>element 1</p>
<p>element 2</p>
<p>element 3</p>
Here's a .map() plain JavaScript. Details commented in Snippet:
SNIPPET
// Collect all <path>s in a NodeList
var paths = document.querySelectorAll('path');
/* Use map() method to convert NodeList
|| into an array and to use getAttribute()
|| method on each of the <path>'s stroke
|| and finally return an array of stroke
|| values (ie colors)
*/
var colors = Array.prototype.map.call(paths, function(obj) {
var shade = obj.getAttribute('stroke');
return shade;
});
console.log(colors);
<svg height="400" width="400">
<path d="M 5.5 21.5 l 85 200" stroke="#4990E2" stroke-width='5'/>
<path d="M 7.5 21.5 l 187 170" stroke="#000" stroke-width='15'/>
<path d="M 8.5 41.5 l 300 10" stroke="#F00" stroke-width='5'/>
</svg>

How to use Jointjs and SVG to draw element tools

I'm new to javascript and SVG and I have no graphical programming background and this is my first project using all of those. So I started to make a custom element just like Mike Goodwin answer proposed and I ended with this code after editing it:
joint.shapes.tools.tooledElement = joint.shapes.basic.Generic.extend({
toolMarkup: [
'<g class="element-tools">',
'<g class="element-tool-remove"><circle fill="red" r="11" stroke="black" stroke-width="1"/>',
'<path transform="scale(.7) translate(-16, -16)" stroke="black" stroke-width="1" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z"/>',
'<title>Remove this element from the model</title>',
'</g>',
'<g class="element-tool-link"><circle fill="green" r="11" cx="160" cy="40" stroke="black" stroke-width="1"/>',
'<path transform="scale(.7) translate(-16, -16)"/>',
'<title>creates a new link</title>',
'</g>',
'</g>'
].join(''),
defaults: joint.util.deepSupplement({
attrs: {
text: { 'font-weight': 400, 'font-size': 'small', fill: 'black', 'text-anchor': 'middle', 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle' }
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
Which works properly. Now I would like to draw some line on the green circle and to make the red circle into a red square. To achieve this I looked at this tutorial on paths to draw and this tutorial on basic shapes. But if I try to make a line on the green circle like this:
'<path transform="scale(.7) translate(-16, -16)" stroke="black" stroke-width="1" d="x y L 10 10 " />'
it won't draw anything. They do say " If your cursor already was somewhere on the page, no line is drawn to connect the two places." and that's why I omitted the "M" from path.
So here comes the first question: How can I draw the line on the center of the green circle without starting from the previous last point defined on any other path?
To make the red square I tried the exactly example from the second tutorial changing the fill (as a test):
//first line to test
<rect x="10" y="10" width="30" height="30" stroke="black" fill="red" stroke-width="5"/>
or
//second line to test
<rect x="60" y="10" rx="10" ry="10" width="30" height="30" stroke="black" fill="red" stroke-width="5"/>
The result of the first line would be the element at which the tools are being used to be draw again above itself like this:
And the second line would end up on nothing being show.
So here are the next questions:
Why did the results from the first line got like that?
and
How can I change the red circle into any other shape?
UPDATE:
About the line draw:
'<path transform="scale(.7) translate(-16, -16)" stroke="black" stroke-width="1" d="M150 150 H 5 V 5 H 5 z" />'
If I use this code for example, this is the result:
'<path transform="scale(.7) translate(-16, -16)" stroke="black" stroke-width="1" d="M150 150 H 5 V 5 H 5 z" />'
If I use this other code then the result will be:
The tutorial led me to believe that M is defining the start point but changing the translate(-16, -16) to something else made the correct start point be possible. So its the translate attribute combined with M that set the starting point.
The first question as been answered on update. As for the second question (concerning the red square conflicting with the element shape inside a rect) this was my work around:
Use the path to draw the tools elements instead of using basic shapes, that way it won't conflict with the element shape.
Example:
'<g class="element-tool-link"><circle fill="green" r="11" cx="160" cy="40" stroke="black" stroke-width="1"/>',
'<path transform="scale(.7) translate(-16, -16)"/>',
'<title>creates a new link</title>',
'</g>'
would turn into:
'<g class="element-tool-link">',
'<path d="M33 0 a 11 11 0 1 0 0.0001 0z " stroke="black" fill="lightblue" stroke-width="1"/>',
'<path transform="scale(.7) " stroke="black" stroke-width="1" d="M58,16 H 37 "/>',
'<title>creates a new link</title>',
'</g>'
where '<path d="M33 0 a 11 11 0 1 0 0.0001 0z " stroke="black" fill="lightblue" stroke-width="1"/>' defines a circle shape.

Recognize point(x,y) is inside svg path or outside

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.

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