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>
Related
Let's say that we have the following simple SVG element:
const svg = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M100 100C100 44.7715 55.2285 -1.95703e-06 0 -4.37114e-06L-4.37114e-06 100L100 100Z" fill="current"/>
</g>
</svg>`
The svg variable is a string and I want to extract all the child nodes from the svg (the <g> element for the specific example but can be more elements).
How can I do that without using a DOM manipulation library like JQuery?
Version 2
You can use str.split() to extract all the child elements from the <svg> as a string.
Working Example:
const svg = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M100 100C100 44.7715 55.2285 -1.95703e-06 0 -4.37114e-06L-4.37114e-06 100L100 100Z" fill="current"/>
</g>
</svg>`;
let g;
g = svg.split('xmlns="http://www.w3.org/2000/svg">')[1];
g = g.split('</svg>')[0];
g = g.trim();
console.log(g);
Version 1
I want to extract the content from the <svg> which is the
element inside it.
If I've understood correctly:
you want to extract only the <g> element
you know that the <svg> contains a <g> element
you know that there is only one <g> element
you can use str.split() to extract the <g> from the <svg>.
Working Example:
const svg = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M100 100C100 44.7715 55.2285 -1.95703e-06 0 -4.37114e-06L-4.37114e-06 100L100 100Z" fill="current"/>
</g>
</svg>`;
let g;
g = svg.split('<g ')[1];
g = g.split('</g>')[0];
g = '<g ' + g + '</g>'
console.log(g);
Either use JavaScripts built in DOMParser
var p= new DOMParser()
var d= p.parseFromString(svg,"text/html")
var g= d.querySelector("svg g")
OR make your own html/xml parser from scratch by looping through each character, checking for <, if found look for some letters next for the tag, if found look for other >, get all content in between for attributes, rinse and repeat
I am using D3 selectAll to perform a transformation. I have around 260 paths but only 80 of them have a class.
How should I select all the paths that have class?
You don't need D3 to check if an element has a class, but here is a D3-based answer. When using a D3 getter...
selection.attr("class")
... it will return null for elements without a class.
Therefore, all you need is checking the getter.
For instance, suppose you have this SVG with 5 paths, 3 of them having a class:
<svg>
<path></path>
<path class="foo"></path>
<path class="bar"></path>
<path class="baz"></path>
<path></path>
</svg>
By using a getter inside a filter we can get only the elements with a class, even if the classes are different:
const pathsWithClass = d3.selectAll("path")
.filter(function() {
return d3.select(this).attr("class")
});
console.log("Elements with class: " + pathsWithClass.size())
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg>
<path></path>
<path class="foo"></path>
<path class="bar"></path>
<path class="baz"></path>
<path></path>
</svg>
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 creating several click-able maps - and have been unsuccessful in trying to find how to access a tag for a path so that I can make all the different counties in the map clickable. (the SVG file is incredibly complicated... this is the structure and I can't change it - it's getting automatically generated for all the different states):
<svg width="932.25827pt" height="569.25354pt" viewBox="0 0 932.25827 569.25354" enable-background="new 0 0 932.25827 569.25354"
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<g id="Layers">
<g id="Ohio_Counties">
<clipPath id="SVG_CP_1">
<path d="M0,569.25354L0,0L932.25827,0L932.25827,569.25354L0,569.25354z"/>
</clipPath>
<path clip-path="url(#SVG_CP_1)" fill="none" stroke="#4E4E4E" stroke-width="0.48003" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" d="
M374.18483,441.14215L375.38491,440.90214L375.62493,440.90214L376.58499,440.66213L376.82501,440.66213L377.54505,440.42212
L378.02508,440.42212L378.2651,440.42212L378.74513,440.42212L379.46518,440.66213L381.14529,440.66213L381.38531,440.66213L
381.62532,440.66213L382.10536,440.66213L382.8254,440.66213L384.50551,440.90214L384.74553,440.90214L386.18563,440.90214L386.42564,441.38217
L386.42564,441.62218L386.42564,441.86219L386.66566,441.86219L386.90567,441.86219L386.90567,442.1022L386.90567,442.34221L"/>
</path>
</g>
</g>
/* There are about 87 more paths in the file... this is just a part of one.*/
I'm using jquery.svg - but haven't found the solution in the documentation.
Here's the javascript:
function changeState(newState){
nS = newState.replace(/\s/g, '');
try {
var map = 'mapLayers/AEP'+nS+'Counties.svg';
$("#contentCounties").empty();
var currentCountyMap = $('#contentCounties').svg({loadURL: loc, onLoad: addClickEvents(this)});
} catch(err){
alert("the map " + currentCountyMap + " does not exist");
}
}
function addClickEvents(){
// this is where I want to put the code to
// add click events to the individual paths.
// Whether it's running a loop or using a jquery selector
// I'm fine with either.
// This is, in theory, what I think I want - but alas, it is wrong:
var svg=document.getElementByTagName("path");
alert(svg);
}
Looks like you've just left the s out of getElementsByTagName
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