Dynamic CSS selection within a group, SVG, Path - javascript

I'm learning CSS and working on a problem involving dynamic color selection of a path.
I've a SVG which can have any number of paths (1+) and there are pre-defined colors which should be assigned to paths depending on their order. (A web service returns a collection of paths).
For simplicity, Let's say I've 4 known paths and there are two colors possible: Green and deepPink.
Question: How can I assign following colors dynamically (without creating individual classes for each path):
Path1: Green
Path2: deepPink
Path3: Green
Path4: deepPink
.path_group {
stroke="black";
fill="none";
stroke-width="2";
}
.myPath{
stroke:deepPink;
stroke-width:1.3;
}
/*
Color1: Green
Color2: deepPink
*/
<svg width="100%" height="100%" viewBox="0 0 260 200"
xmlns="http://www.w3.org/2000/svg">
<g class="path_group" >
<path class="myPath" d="M10,25 L110,10" />
<path class="myPath" d="M10,35 L110,20" />
<path class="myPath" d="M10,45 L110,30" />
<path class="myPath" d="M10,55 L110,40" />
</g>
</svg>
I'm more interested in doing it in natively using CSS/JS instead of adding any framework or library to my project. Suggestion regarding any particular concept is appreciated.
Demo: Codepen
Current approach
this.colors = ["green", "deepPink"];
this.getColor = function (pathIndex) {
return this.colors[pathIndex % this.colors.length];
};

You can use CSS3 :nth-child() selector (w3schools) as this:
.myPath { stroke-width:1.3; }
.path_group .myPath:nth-child(4n+1)
{ stroke:red; }
.path_group .myPath:nth-child(4n+2)
{ stroke:blue; }
.path_group .myPath:nth-child(4n+3)
{ stroke:green; }
.path_group .myPath:nth-child(4n+4)
{ stroke:deepPink; }
See updated codepen here.

Related

Issues with conversion of svg element to .svg

After hours of looking for a solution here and there, I come here to ask for help.
I'm developing a customization module using <svg> element and Javascript.
In the end, I convert and download the SVG element in a .svg file but the problem is that some elements like the <text> ones are not in the right place when opening the file with Illustrator, it doesn't seem to care about the x and y attributes.
When I open the file in a browser, everything is in the right place.
Here is the HTML of the downloaded .svg file I get, all at the right place (the browser read the HTML attributes well I guess) : https://codepen.io/florianeychenne/pen/vYRgMWx
Here is the rendering in Illustrator :
The browser contains it to the first SVG element, which is width="833" height="396". Thats why you don't see the 'Exemple', it is contained to that first SVG tag. Illustrator does not contain anything, as it is not a browser viewport. I think that if you use only 1 SVG tag, it will solve your issue. It should then render the same in both software.
Some ideas for a workaround
convert/combine all transformations to matrix():
this way we circumvent problems with transform-origin (Illustrator
apparently can't use it - rotations will have a wrong pivot point)
convert nested svg elements to <g> group elements
some attributes like textLength won't work at all in AI - you might use a condensed font instead.
Working example
// getTransformToElement polyfill
SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(
toElement) {
return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
function fixNestedSvgs() {
let svgs = document.querySelectorAll('svg');
let nested = document.querySelectorAll('svg svg');
// all transformations to matrix()
svgs.forEach(function(svg) {
let children = [...svg.children];
children.forEach(function(child) {
transFormToMatrix(child);
styleToAttribute(child, ['fill', 'stroke', 'stroke-width']);
});
})
// nested to groups
nested.forEach(function(svg) {
let parent = svg.parentNode.closest('svg');
nestedSvgToGroup(svg);
svgMarkup.value = parent.outerHTML;
})
}
// all transformations to matrix()
function transFormToMatrix(el) {
let type = el.nodeName.toLowerCase();
let matrixString = '';
let types = ["path", "polygon", "polyline", "rect", "ellipse", "circle", "line", "text", "g", "svg"];
if (types.indexOf(type) !== -1) {
// get el matrix
let matrix = el.getTransformToElement(el.parentNode.closest("svg"));
let [a, b, c, d, e, f] = [
matrix.a,
matrix.b,
matrix.c,
matrix.d,
matrix.e,
matrix.f
];
matrixString = [a, b, c, d, e, f].join(" ");
//exclude non transformed elements
if (matrixString != "1 0 0 1 0 0") {
el.setAttribute("transform", `matrix(${matrixString})`);
el.removeAttribute("transform-origin");
}
}
return matrixString;
}
// convert fill styles to attributes
function styleToAttribute(el, attributes = ['fill', 'stroke', 'stroke-width']) {
let types = ["path", "polygon", "polyline", "rect", "ellipse", "circle", "line", "text", "g", "svg"];
let type = el.nodeName.toLowerCase();
if (types.indexOf(type) !== -1) {
let style = window.getComputedStyle(el);
attributes.forEach(function(attName) {
let value = style.getPropertyValue(attName);
if (value !== 'rgb(0, 0, 0)') {
el.setAttribute(attName, value);
}
})
}
}
//convert nested svgs to groups
function nestedSvgToGroup(svg) {
let svgSub = svg;
if (svg.parentNode) {
let parent = svg.parentNode.closest('svg');
let svgSubChildren = [...svgSub.children];
let groupMatrix = transFormToMatrix(svgSub);
//replace nested svg with group - apply matrix
let group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
group.classList.add('svgNest');
group.setAttribute('transform', `matrix( ${groupMatrix} )`);
//copy children to group
svgSubChildren.forEach(function(child, i) {
group.appendChild(child);
})
//remove nested svg
svgSub.replaceWith(group);
}
}
svg {
width: 80%
}
<svg class="center" viewBox="0 0 833 396" xmlns="http://www.w3.org/2000/svg">
<style>
#import url("https://fonts.googleapis.com/css2?family=Audiowide&family=Black+Ops+One&family=Faster+One&family=Fugaz+One&family=Racing+Sans+One&display=swap");
.fillColor {
fill: currentColor;
}
.strokeColor {
stroke: currentColor;
fill: none;
stroke-width: 5px;
}
.fillStrokeColor {
stroke: currentColor;
fill: currentColor;
stroke-width: 1px;
}
text {
font-family: 'Fugaz One', cursive;
}
</style>
<rect id="allCarpet" x="0" y="0" width="833" height="396"></rect>
<rect class="fillColor" id="foamCarpet" x="10" y="10" width="813" height="376" style="color:#ffd100"></rect>
<!-- <rect class="fillColor" id="leftLogo" x="22" y="105" width="84" height="187" style="color:white" /> -->
<text class="svg" x="580" y="345" font-size="15" id="topNickname" fill="black" textLength="70"
lengthAdjust="spacingAndGlyphs">FLORIAN</text>
<text class="svg" x="590" y="368" font-size="30" id="topNickname" fill="black" textLength="80"
lengthAdjust="spacingAndGlyphs">FLORIAN</text>
<text class="svg" x="-25" y="55" font-size="60" id="topNumber" fill="black" textLength="80"
lengthAdjust="spacingAndGlyphs" transform="rotate(180)" transform-origin="42px 42px">00</text>
<text class="svg" x="725" y="366" font-size="60" id="botNumber" fill="black" textLength="80"
lengthAdjust="spacingAndGlyphs">00</text>
<svg version="1.1" class="nested" id="Calque_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="1px" y="0px" viewBox="0 0 197 100"
style="enable-background:new 0 0 197 100;" xml:space="preserve" width="150px">
<style type="text/css">
.st0 {
fill: #CA0D25;
}
.st1 {
fill: none;
}
.st2 {
fill: #FF0000;
}
.st3 {
fill: #FEFEFE;
}
</style>
<path class="st1" d="M7.2,3.8v92.5h182.7V3.8H7.2z M186.8,93.2H10.2V6.8h176.5L186.8,93.2L186.8,93.2z"
transform="rotate(90)" transform-origin="center"></path>
<path class="st2" fill="red" d="M66.9,62.1v0.3c-0.1-0.1-0.2-0.2-0.2-0.3H66.9z M73.7,41.5c-5.6,0-6.9,5-6.9,9.6c0,4.6,1.3,9.5,6.9,9.5
c5.2-0.1,6.9-4.7,6.9-9.5C80.5,46.4,78.7,41.6,73.7,41.5z M43.4,53.1c-0.2,0.1-0.5,0.1-0.7,0.1c-2.5,0.5-5.1,1.6-5.1,4.6
c0,3.1,2.1,3.4,4.7,3.4c0.4,0,0.7,0,1.1,0c5.2-0.5,5.7-4.4,5.7-6.1L49,51.5C47.8,52.6,45.7,52.8,43.4,53.1z M186.8,6.8v86.3H10.2
V6.8H186.8z M57.5,66.4c-1.3-1.7-0.9-3.9-0.9-6.6V44.3c0-7.6-7.8-8.4-13.3-8.4c-6.2,0.1-11.9,2.3-12.3,9.5l7.3,0.1
c0.3-2.7,2.1-4,5.1-4.1c2.7,0,5.8,0.3,5.8,4c0,2.4-2.6,2.6-5.8,2.7c-1.4,0-2.8,0.1-4.3,0.3c-4.9,0.7-9.2,2.6-9.2,9.3
c0,5.3,4.1,9.2,9.9,9.2c1.2,0,2.1,0,3.3-0.2l0.3,0c2.2-0.4,4.3-1.3,6-3.1c0,0.9,0.6,1.9,0.9,2.8L57.5,66.4L57.5,66.4z M88.7,51.3
c0-8.5-4.2-16-13.5-16c-0.5,0-1,0-1.5,0.1c-3.1,0.4-5.7,1.8-7.4,4.5h-0.1v-3.1h-6.9v37.8h7.6V62.4c1.6,2.2,4.1,3.6,6.8,4.1
c0.6,0.1,1.3,0.1,2,0.1C84.3,66.7,88.7,59.2,88.7,51.3z M106.1,35.9c-4.3,0-7.1,1.8-8.8,5.5l0-4.8h-7.1v29.8h7.6V53
c0-6.8,3.8-9.5,8.2-9.5L106.1,35.9L106.1,35.9z M116.5,36.7h-7.6v29.7h7.6V36.7z M116.5,25.4h-7.6v6.8h7.6V25.4z M126.5,25.4h-7.6
v41.1h7.6V25.4z M136.7,36.7H129v29.7h7.6V36.7z M136.7,25.4H129v6.8h7.6V25.4z M166.7,66.3c-1.3-1.7-0.9-3.9-0.9-6.6V44.2
c0-7.6-7.8-8.4-13.3-8.4c-6.2,0.1-11.9,2.3-12.3,9.5l7.3,0.1c0.3-2.7,2.1-4,5.1-4.1c2.7,0,5.8,0.3,5.8,4c0,2.4-2.6,2.6-5.8,2.7
c-1.4,0-2.8,0.1-4.3,0.3c-4.9,0.7-9.2,2.6-9.2,9.3c0,5.3,4.1,9.2,9.9,9.2c1.2,0,2.1,0,3.3-0.2l0.3,0c2.2-0.4,4.3-1.3,6-3.1
c0,0.9,0.6,1.9,0.9,2.8L166.7,66.3L166.7,66.3z M152.5,53c-0.2,0.1-0.5,0.1-0.7,0.1c-2.5,0.5-5.1,1.6-5.1,4.6c0,3.1,2.1,3.4,4.7,3.4
c0.4,0,0.7,0,1.1,0c5.2-0.5,5.7-4.4,5.7-6.1l-0.1-3.7C156.9,52.5,154.8,52.7,152.5,53z" transform="rotate(90)"
transform-origin="center"></path>
<path class="st3"
d="M129.2,25.4h7.6v6.8h-7.6V25.4z M57.7,66.4h-7.3c-0.3-0.9-0.8-1.9-0.9-2.8c-1.6,1.8-3.7,2.7-6,3.1l-0.3,0
c-1.2,0.2-2.1,0.2-3.3,0.2c-5.8,0-9.9-4-9.9-9.2c0-6.7,4.3-8.6,9.2-9.3c1.5-0.2,2.9-0.3,4.3-0.3c3.2-0.2,5.8-0.4,5.8-2.7
c0-3.7-3-4-5.8-4c-2.9,0-4.8,1.3-5.1,4.1l-7.3-0.1c0.5-7.1,6.1-9.4,12.3-9.5c5.4,0,13.3,0.8,13.3,8.4v15.5
C56.9,62.4,56.5,64.7,57.7,66.4z M49.3,55.1l-0.1-3.7c-1.2,1.1-3.3,1.3-5.6,1.6c-0.2,0.1-0.5,0.1-0.7,0.1c-2.5,0.5-5.1,1.6-5.1,4.6
c0,3.1,2.1,3.4,4.7,3.4c0.4,0,0.7,0,1.1,0C48.8,60.7,49.3,56.8,49.3,55.1z M166.9,66.3h-7.3c-0.3-0.9-0.8-1.9-0.9-2.8
c-1.6,1.8-3.7,2.7-6,3.1l-0.3,0c-1.2,0.2-2.1,0.2-3.3,0.2c-5.8,0-9.9-4-9.9-9.2c0-6.7,4.3-8.6,9.2-9.3c1.5-0.2,2.9-0.3,4.3-0.3
c3.2-0.2,5.8-0.4,5.8-2.7c0-3.7-3-4-5.8-4c-2.9,0-4.8,1.3-5.1,4.1l-7.3-0.1c0.5-7.1,6.1-9.4,12.3-9.5c5.4,0,13.3,0.8,13.3,8.4v15.5
C166,62.3,165.6,64.6,166.9,66.3z M158.4,55l-0.1-3.7c-1.2,1.1-3.3,1.3-5.6,1.6c-0.2,0.1-0.5,0.1-0.7,0.1c-2.5,0.5-5.1,1.6-5.1,4.6
c0,3.1,2.1,3.4,4.7,3.4c0.4,0,0.7,0,1.1,0C157.9,60.7,158.4,56.7,158.4,55z M88.9,51.2c0,8-4.4,15.4-13.1,15.4c-0.6,0-1.3,0-2-0.1
c-2.7-0.4-5.2-1.9-6.8-4.1v-0.3h-0.2c0.1,0.1,0.2,0.2,0.2,0.3v12.2h-7.6V36.8h6.9v3.1h0.1c1.7-2.7,4.3-4.2,7.4-4.5
c0.5,0,1-0.1,1.5-0.1C84.8,35.2,88.9,42.8,88.9,51.2z M80.7,51c0-4.6-1.8-9.4-6.9-9.6c-5.6,0-6.9,5-6.9,9.6c0,4.6,1.3,9.5,6.9,9.5
C79.1,60.4,80.7,55.8,80.7,51z M129.2,66.4h7.6V36.7h-7.6V66.4z M119.1,66.5h7.6V25.4h-7.6V66.5z M109.1,66.4h7.6V36.7h-7.6V66.4z
M97.5,41.4l0-4.8h-7.1v29.8h7.6V53c0-6.8,3.8-9.5,8.2-9.5v-7.6C102,35.9,99.2,37.7,97.5,41.4z M109.1,32.2h7.6v-6.8h-7.6V32.2z"
transform="rotate(90)" transform-origin="center"></path>
</svg>
<text class="svg" x="323" y="586" font-size="100" id="rightText" fill="black" transform="rotate(-90)" transform-origin="center" textLength="187" lengthAdjust="spacingAndGlyphs">EXEMPLE</text>
<polyline class="strokeColor" id="centerPolygon" points="144,103 642,103 689,153 689,294 192,294 144,246 144,100.5" style="color:#010101;"></polyline>
<polyline class="fillStrokeColor" id="bottomPolygon" points="10,374 525,374 547,347 10,347" style="color:#009afb"></polyline>
<polyline class="fillStrokeColor" id="topPolygon" points="823,22 308,22 286,49 823,49" style="color:#009afb">
</polyline>
<polyline class="fillStrokeColor" id="littleBottomPolygon" points="10,340 553,340 560,332 10,332" style="color:#fefefe"></polyline>
<polyline class="fillStrokeColor" id="littleTopPolygon" points="823,57 280,57 273,65 823,65" style="color:#fefefe"></polyline>
</svg>
<p><button type="button" onclick="fixNestedSvgs()"> Fix nested svg</button></p>
<textarea id="svgMarkup" style="width:100%; min-height:20em"></textarea>
Drawbacks
This conversions might fail in browsers struggling with transform-origin (like some safari/webkit versions).
Some graphic applications might also have issues parsing inherited fill attributes like fill:currentColor.
The above example includes a helper styleToAttribute() to inline some attributes.

JavaScript local storage mixing up toggle keys?

I am a Javascript beginner trying to use local storage to store a set of button toggle states (on/off). My goal is to be able to toggle a set of svg fills, on and off, and have the states stored, returning to the selected state, after a browser refresh.
//LOOP 1 - GET STORED VALUES AND SET SVGs ACCORDINGLY
function grabStoredStates(){
for (let i=0; i < localStorage.length; i++) {
let key = localStorage.key(i);
let value = localStorage.getItem(key);
if (value == "on") {
//"cbox" below is from LOOP 2 - maybe it's causing the problem
cbox[i].style.fill = "coral";
}
}
}
//LOOP 2 - TOGGLE AND STORE TOGGLE STATES
let cbox = document.querySelectorAll(".toggleMe");
for (let i = 0; i < cbox.length; i++) {
cbox[i].addEventListener("click", function() {
if (cbox[i].style.fill == "coral") {
cbox[i].style.fill = "white";
localStorage.setItem(cbox[i].id, "off");
}else{
cbox[i].style.fill = "coral";
localStorage.setItem(cbox[i].id, "on");
}
});
}
<body onload="grabStoredStates()">
<svg height="300" width="700>
<g pointer-events="all">
<rect class="toggleMe" cursor:pointer; id="rect1" width="100" height="100" x="50" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
<rect class="toggleMe" cursor:pointer; id="rect2" width="100" height="100" x="175" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
<rect class="toggleMe" cursor:pointer; id="rect3" width="100" height="100" x="300" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
<rect class="toggleMe" cursor:pointer; id="rect4" width="100" height="100" x="425" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
</g>
</svg>
</body>
My problem is that if you load the page, and then open local storage in Chrome Dev Tools, (Application > Storage > Local Storage), and then click the rectangles in sequence from left to right you should see this, as they toggle correctly from light blue to orange:
Key Value
rect1 on
rect2 on
rect3 on
rect4 on
In local storage, the keys and values are, to that point, matched properly, showing all squares to be orange, as intended. However, if you then click rect3, (the third rectangle from the left), for instance, toggling it to light blue, with an expected corresponding "off" state -- on refreshing the page, the stored values appear to get jumbled, with the loop apparently renumbering the rectangles as if it was starting from [0] -- ignoring the intended key/value pairing. You then see this:
Key Value [PROBLEM]
rect1 on
rect2 on [ --> Displays as Off]
rect3 off [ --> Displays as on]
rect4 on
Is there any way that I can make the keys and values stay together, regardless of loop called. The "grabStoredStates()" loop appears to work, as tested by manually changing values and refreshing... I suspect, then, that it has to do with the transition between two separate for loops? Maybe the "cbox" loop should also be in a function? I looked at the prior stack overflow "can't use booleans in local storage" answer, but as I'm using "on" and "off" that doesn't seem to apply. I also saw a jQuery answer, but I'm not familiar with that library yet. (I wouldn't, however, turn down a jQuery answer if offered.)
Again, my original goal was to be able to toggle svg fills on and off and have the states stored after browser refresh. I'd love a more elegant way if there is one...
Thanks everyone, in advance, for your help.
Here I tried to make a simpler version. The presentation of the <rect>s relies on an attribute called data-state. When the document is loaded the attributes will be updated (I commented out the code for localStorage and replaced it with the array testArr). An event listener on <g> will handle click events and test if a <rect> was clicked, if so, the attribute is updated/toggled. After that the values are all saved to localStorage/testArr.
var cbox;
var testArr = ['on', 'off', 'off', 'on']; // for testing
document.addEventListener('DOMContentLoaded', e => {
cbox = document.querySelectorAll(".toggleMe");
/* // Code for localStorage:
Array.from(Array(localStorage.length).keys()).forEach(i => {
let key = localStorage.key(i);
let value = localStorage.getItem(key);
cbox[i].attributes['data-state'] = value;
});*/
testArr.forEach((state, i) => cbox[i].attributes['data-state'].value = state); // for testing
document.querySelector('g').addEventListener('click', e => {
if(e.target.classList.contains('toggleMe')){
let currentValue = e.target.attributes['data-state'].value;
e.target.attributes['data-state'].value = (currentValue == 'on') ? 'off' : 'on';
/* // Code for localStorage:
[...cbox].forEach((elm, i) => {
let value = elm.attributes['data-state'].value;
localStorage.setItem(`Rect${i}`, value);
});*/
testArr = [...cbox].map(elm => elm.attributes['data-state'].value); // for testing
console.log(testArr.join(',')); // for testing
}
});
});
rect.toggleMe {
cursor: pointer;
width: 50px;
height: 50px;
stroke: black;
stroke-width: 2px;
}
rect[data-state="on"] {
fill: aliceblue;
}
rect[data-state="off"] {
fill: coral;
}
<body>
<svg height="300" width="300">
<g>
<rect class="toggleMe" data-state="off" x="50" y="80" />
<rect class="toggleMe" data-state="off" x="110" y="80" />
<rect class="toggleMe" data-state="off" x="170" y="80" />
<rect class="toggleMe" data-state="off" x="230" y="80" />
</g>
</svg>
</body>

How to select all elements that have (any) class

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>

Snap.load() an svg img and animate the fill color of it's child nodes

I'm using Snap.svg in order to animate an SVG image on hover. In this case, I'm trying to make the image a little bit bigger (1.1x) and then change the fill color. On hover, the image scales correctly, but the fill color is unchanged. I have a suspicion it's because the image consists of three child elements which is where the fill colors reside, but I'm unable to figure out how to access them.
Here's the html:
<svg id="mySvg" width="200" height="200"
viewBox="0 0 2000 2000" preserveAspectRatio="none"></svg>
And here's the javascript:
var s = Snap("#mySvg");
var g = s.group();
var tux = Snap.load("3d_glasses.svg", function (loadedFragment) {
g.append(loadedFragment);
g.hover(hoverover, hoverout);
});
var hoverover = function () {
g.animate({
transform: 'scale(1.1)',
fill: '#FF0000'
}, 1000, mina.bounce);
};
var hoverout = function () {
g.animate({
transform: 'scale(1)',
fill: '#252525'
}, 1000, mina.bounce);
};
My SVG image file (3d_glasses.svg) looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="1024px" height="1024px" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="nonzero" clip-rule="evenodd" viewBox="0 0 10240 10240" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>3D_glasses icon</title>
<path id="curve2" fill="#252525" d="M6669 6080c-89,-175 -174,-356 -261,-531 -39,-78 -85,-152 -137,-222 -131,-175 -191,-356 -191,-575l0 -592 2560 0 0 1920 -1971 0z"/>
<path id="curve1" fill="#252525" d="M3571 6080c89,-175 174,-356 261,-531 39,-78 85,-152 137,-222 131,-175 191,-356 191,-575l0 -592 -2560 0 0 1920 1971 0z"/>
<path id="curve0" fill="#252525" d="M960 3200l8320 0c176,0 320,144 320,320l0 3200c0,176 -144,320 -320,320l-2804 0c-249,0 -462,-132 -573,-354l-354 -707c-83,-167 -243,-266 -429,-266 -186,0 -346,99 -429,266l-354 707c-111,222 -324,354 -573,354l-2804 0c-176,0 -320,-144 -320,-320l0 -3200c0,-176 144,-320 320,-320zm5162 2492c120,240 249,708 547,708l1971 0c176,0 320,-144 320,-320l0 -1920c0,-176 -144,-320 -320,-320l-2560 0c-176,0 -320,144 -320,320l0 592c0,288 83,536 255,767 41,54 76,113 107,173zm-2551 708c123,0 229,-65 285,-175 90,-177 174,-356 262,-533 31,-60 67,-119 107,-173 172,-231 255,-479 255,-767l0 -592c0,-176 -144,-320 -320,-320l-2560 0c-176,0 -320,144 -320,320l0 1920c0,176 144,320 320,320l1971 0z"/>
</svg>
defghi is correct in that the fill value of the elements is causing the issue.
I'm going to give an alternative answer though, and I guess its down to personal preference in terms of one may work better with the rest of your code and solution.
Rather than remove the fill, I would probably use a css transition, that way the original fills are left intact. So it would work something like...
s.select("g").hover( hoverFunc, hoverOut );
function hoverFunc() {
this.selectAll('path').forEach( function( el ){
el.attr({ class: 'redfadein' }) } );
this.animate({ transform: 's1.1,1.1' }, 1000);
};
function hoverOut() {
this.selectAll('path').forEach( function( el ){
el.attr({ class: 'redfadeout' }) } );
this.animate({ transform: 's1,1' }, 1000);
};
and css
redfadein {
transition: fill 1s ease;
fill:red;
}
.redfadeout {
transition: fill 1s ease;
}
jsfiddle
You may also just want to put a white filled background (or small opacity) on the space in the middle of the path so it doesn't hover out when over the white space. You could also move the scale to css as well to keep it all in one place.
You should remove fill attributes from path elements in source SVG file.
When path elements have fill attribute, the fill value of g element contains these path is ignored for rendering.
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1024px" height="1024px" fill-rule="nonzero" viewBox="0 0 10240 10240" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>3D_glasses icon</title>
<path id="curve2" d="M6669 6080c-89,..."/>
<path id="curve1" d="M3571 6080c89,-175 174..."/>
<path id="curve0" d="M960 3200..."/>
</svg>
If you can't edit SVG file, you can remove fill attributes by calling removeAttribute method of SVGDOM.

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