how to change an attribute of an svg file with javascript? - javascript

I know this has been answered before but no solutions are working for me. I want to render an svg file on an html page and access its code elements from javascript. I want to change the attributes of specific elements. Specifically I want to change add an attribute style="fill:#FFFFFF;". Later I will want to remove this. I want to access the element with the id property of the element. I tried doing this with a plethora of javascript code and rendering the svg as an img and an object and nothing works. Is this possible? If so how? Thanks in advance.

You can create a react component for your SVG and control their properties with props in react way:
const SampleIcon = ({fill='red',backgroundColor='white', width='300', height='200'}) => (
<svg version="1.1"
width={width} height={height}
xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill={backgroundColor} />
<text x="150" y="125" font-size="60" text-anchor="middle" fill={fill}>SVG</text>
</svg>
)
Now use it:
const App = () => (
<div>
<SampleIcon fill='green' backgroundColor='#ff0'/>
<SampleIcon with={400} height={300}/>
</div>
)

There is no way to change the fill property of an SVG you want to display in an img tag. However, you can change the filter property.
So what you want to do is display the svg file like so:
<img class="svg-icon" src="./your.svg"/>
You have to give your img tag a classname (also, do not set the fill property inline).
Then you can simply apply a filter and the color of the svg will be changed, like so:
.your-icon {
filter: invert(14%) sepia(95%) saturate(6069%) hue-rotate(360deg) brightness(108%) contrast(117%);
}
The above filter is red. Use this hex to filter converter to find the right color for you: hex to filter.
Here is a codesandbox demo.

Related

querySelector after using case sensitive setAttributeNS

If I have a case sensitive attribute like nativeAttr I can then use querySelector to find the element by its attribute name.
But if I programmatically add a case sensitive attribute like setAttr, then querySelector no longer finds the html element.
How can I set the case sensitive attribute and also make it work with querySelector?
const node = document.getElementById('node')
node.setAttributeNS(null, 'setAttr', 'set')
console.log('nativeAttr', document.querySelector('[nativeattr]')?.id)
console.log('nativeAttr', document.querySelector('[nativeAttr]')?.id)
console.log('setAttr', document.querySelector('[setattr]')?.id)
console.log('setAttr', document.querySelector('[setAttr]')?.id)
// the attribute is set in camelCase correctly
console.log(node.getAttributeNS(null, 'setAttr'))
// here are the names of the attributes; seems that nativeAttr is lowercase
console.log('nativeAttr', node.attributes[1].name, node.attributes[1].localName)
console.log('setAttr', node.attributes[2].name, node.attributes[2].localName)
<div id="node" nativeAttr="native"></div>
Case sensitive attributes are used by svg elements, so it's a valid use case. For example:
// only one viewBox element
console.log(document.querySelectorAll('[viewBox]').length)
// add the viewBox attribute to the second svg
const svg2 = document.getElementById('svg2')
svg2.setAttributeNS(null, 'viewBox', '0 0 50 50')
// now both svg elements show up
console.log(document.querySelectorAll('[viewBox]').length)
<svg id="svg1" width="30" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50"/>
</svg>
<svg id="svg2" width="30" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50"/>
</svg>
So, as you can see it works for svg, but not for regular html elements.
According to the spec, uppercase letter are allowed in the attribute names.
In the HTML syntax, attribute names, even those for foreign elements, may be written with any mix of ASCII lower and ASCII upper alphas.
Attributes are always processed in lower case, so if you provide the attribute nativeAttr, this will be transformed to nativeattr.
As it is described in the HTML standard:
All attribute names on HTML elements in HTML documents get ASCII-lowercased automatically
It is recommended to use kebab-case with attribute names. Check out this minimal example below:
const el = document.getElementById('node');
console.log('before', el.getAttributeNS(null, 'native-attr' ))
el.setAttributeNS(null, 'native-attr', 'changed');
console.log('after', el.getAttributeNS(null, 'native-attr' ))
<div id="node" native-attr="native"></div>
Also read here: are html5 data attributes case insensitive?
The appropriate character for selecting namespaced attributes is the pipe character (|). If there is no namespace prefix, you just use nothing before the pipe, so [*|setAttr] is supposed to select your element. However, in my testing, it does not do so, via CSS nor querySelector:
const node = document.getElementById('node');
node.setAttributeNS(null, 'setAttr', 'set');
node.setAttributeNS('https://example.com/namespace', 'ns:someAttr', 'set');
console.log('html', node.outerHTML);
console.log('nativeAttr', document.querySelector('[|nativeAttr]')?.id);
console.log('setAttr', document.querySelector('[*|setAttr]')?.id);
console.log('someAttr', document.querySelector('[*|someAttr]')?.id);
#namespace ns url("https://example.com/namespace");
div[|nativeAttr] { color: blue; }
div[*|setAttr] { color: red; }
div[ns|someAttr] { color: green; }
<div id="node" nativeAttr="native">Ran</div>
The syntax does not work even when a prefix and namespace is provided, so that seems to be a red herring as well.
This answer is less an answer as it is documentation of findings, along with code demonstrating what should work. The tests were run in Microsoft Edge Version 97.0.1072.62 (Official build) (64-bit) on Window 10 Version 10.0.19043.1466.

How to access the description of a svg element in js?

svg elements can have descriptions as described here
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc
Assuming I got the handle of the circle via getElementById, how can I access the content of the <desc> tag?
Adding an id to the tag cannot be a solution given the SVG comes from an editor that allows me to add id's in the elements, but not to their descriptions.
PS. It's not relevant, but the idea is to add javascript code in the descriptor to be executed (via Eval) to update the owner element.
Thanks!
You can retrieve the content of tag <desc> with textContent.
I made an example for you with the ability to change the content of the <desc> tag through <textarea>, by pressing a button
The first console.log() displays the default content of the <desc> tag.
All subsequent console.log() show modified content.
let desc = document.querySelector('desc');
let textarea = document.querySelector('textarea');
let button = document.querySelector('button');
console.log(desc.textContent.trim());
button.onclick = function() {
desc.textContent = textarea.value;
console.log(desc.textContent.trim());
}
<textarea></textarea>
<button>change desc</button>
<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
<circle cx="5" cy="5" r="4">
<desc>
I'm a circle and that description is here to
demonstrate how I can be described, but is it
really necessary to describe a simple circle
like me?
</desc>
</circle>
</svg>
assuming you've the circle then calling
let desc = circle.getElementsByTagName("desc");
will get you all the desc elements that are descentants of the circle element.
You can loop over those and do what you want with them. Note that you'll get a collection of desc elements so if there's only one element it will be the first and only element in that collection.

Turn shapes in an svg into buttons automatically to display additional information

I have a map drawn in CorelDRAW that shows the location of some company assets. Each one in shown as a square on the map. My aim is to create an interactive version of the map, where the user can click on a square and be shown more information about the asset.
CorelDRAW allows me to specify a name for each shape. Then, if the diagram is exported as an SVG, the shape names are used for the id attributes.
This gives me a map in SVG format; let's say it looks like this:
<svg width="100" height="100">
<rect id="firstAsset" x="25" y="50" width="20" height="20" />
<rect id="secondAsset" x="50" y="15" width="20" height="20" />
</svg>
I want to be able to click on each rectangle and have information displayed about it. A popup would do, such as this example at w3cschools, or a modal box if there's more information to display.
So, I've started by embedding a test svg in an html document. This doesn't mean it will end up on a web server; the file may just end up saved on a SharePoint. Ideally it would end up a single file that can be distributed by email if need be.
Here is what I have so far, using modal boxes: link to js fiddle.
The problem is, the full SVG is going to have dozens more clickable shapes, and if the map is updated in CorelDRAW, I will have to add the onclick() and class attributes to each shape all over again. I need to be able to take information about each asset in some standard format, and then have it automatically added to each asset in the SVG.
I originally envisioned writing a script that would take the SVG file and a file with the information to display. It would search the SVG for id attributes, check for corresponding information to display, and if so make the element clickable to display that information. This is where I'm stuck. Do I need to write a separate program just to maintain this map? Or could this be done using javascript in the document itself?
To recap, I have:
An automatically generated SVG with id attributes against each asset
I will have some information about each asset, which I can put in html format
and I want to be able to click on an asset on the map and show the information about it.
P.S. I'm new to html and javascript.
This code example might help you a bit to get started.
var rect = document.querySelectorAll('rect');
rect.forEach(item=>{
item.addEventListener('click', tooltip);
});
function tooltip(){
console.log(this.id);
}
I have tweaked your fiddle a little please look
$(function () {
$( "#redButtons,#blueButtons" ).on( "click", "rect", function() {
//alert(this.id);
$( "#theInfo" ).html(data[this.id]);
$( "#modal" ).show();
event.stopPropagation();
});
$( "#close,body" ).on( "click", function() {
$( "#modal" ).hide();
});
})
I believe jQuery will help some. jsFiddle somehow support it, but you can embed jquery if you want a single file, or link it from separate file or internet.
The solution I have is in this js fiddle. It uses pure javascript, and the svg can be pasted in unmodified from the graphics application.
Data about each asset is stored in div tags like this:
<!--Information about each asset, that is added to the modal before it's displayed -->
<div id="html_asset1" class="hiddenInfo" data-asset="asset1" data-category="redCategory">
<p>This is some information about asset one.</p>
</div>
The id attribute has to be the same as the corresponding shape in the svg prepended with "html_". The class "hiddenInfo" hides the data. There are two custom attributes. The first is the id attribute of the corresponding shape. The second is just a the name of a class that will be applied to the shape to set the colour.
I then have a little javascript that gets each div element like the above, and uses the custom attributes I just mentioned to turn the corresponding shapes into buttons:
var hiddenInfo = document.getElementsByClassName('hiddenInfo');
var eachAsset;
for (i = 0; i < hiddenInfo.length; i++) {
eachAsset = document.getElementById(hiddenInfo[i].dataset.asset);
eachAsset.className.baseVal += " button " + hiddenInfo[i].dataset.category;
eachAsset.addEventListener('click', displayModal);
}
When any button is clicked, the contents of the modal window is set to the hidden information:
//Populate the information in the modal and then make it visible
function displayModal() {
var hiddenHTMLid = "html_" + this.id
//use the hidden HTML to populate the modal
document.getElementById("theInfo").innerHTML = document.getElementById(hiddenHTMLid).innerHTML;
modal.style.display = "block";
}
Overall it works nicely and keeps the data separate from how it is displayed, which I like.
Thanks for the help.

Selecting g inside SVG tag in selenium

I have just started working in selenium and stuck at some point and need help from experts.
Here is my html
<div id='d3_tree'>
<svg>
<g transform="translate(20,50)>
<g class='node'>
</g>
<g class='node pe_node'>
</g>
<g class='node pe_node'>
</g>
</g>
</svg>
</div>
I need to have all the <g> having class pe_node and invoke context menu on these <g>
I have tried to get the svg like this
node = self.driver.find_elements(By.XPATH, "//div[#id='d3_tree']/'svg']/g")
then I have read that svg can not be selected directly So I tried this
nodes = self.driver.find_elements(By.XPATH, "//div[#id='d3_tree']/*[name()='svg']/g")
and
nodes = self.driver.find_elements(By.XPATH, "//div[#id='d3_tree']/*[local-name()='svg']/g")
But it is still not working for me and I am getting [] in result.
Can anyone guide me how to select the <g> with class pe_node inside svg
Any help will be appreciated
Thanks
You were halfway there, the following should work:
nodes = self.driver.find_elements(By.XPATH, "//div[#id='d3_tree']/*[name()='svg']/*[name()='g']")
Every element inside 'svg' has to be referenced as `/*[name()='']
In this case you can shorten it up a bit with:
nodes = self.driver.find_elements(By.XPATH, "//div[#id='d3_tree']/*/*[name()='g']")
Following xpath should work //div[#id='d3_tree']//g[contains(#class, 'pe_node')]
Could you not select the <svg> element using the tagName?
node = driver.findElement(By.tagName("svg"))
otherNodes = node.findElements(By.Xpath("./g[contains(#class, 'pe_node')]")
You can try this,I am unaware of which language you are using.But the below selenium will might be helpful for you.Nodes will return all the elements under svg tag with having class as "node pe_node".
node = self.driver.find_element(By.XPATH, "//div[#id='d3_tree']/svg]")
nodes = node.find_elements(By.XPATH,"//g[#class='node pe_node']")
You can write like this:
//div[#id='d3_tree']/*[name()='svg']/*[name()='g' and #class='node pe_node']/*[name()='g'][2]

Is it possible to access the SMIL timer from javascript?

I'm trying to use SMIL to animate the typing of text into a field embedded in a SVG. I tried the following code in both Chrome and a SMIL-enable Firefox nightly, but it has no effect:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:html="http://www.w3.org/1999/xhtml">
<foreignObject>
<html:input type="text" value="">
<set attributeName="value" to="Hello World"
begin="0" dur="10s" fill="freeze" />
</html:input>
</foreignObject>
</svg>
The text field appears, but remains empty. So, I thought I would register for the beginEvent and do the substitution manually. To test the events, I added:
<rect id="rect" x="0" y="0" width="10" height="10">
<animate id="dx" attributeName="x" attributeType="XML"
onbegin="console.log('onbegin')"
begin="0s" dur="1s" fill="freeze" from="0" to="-10" />
</rect>
As well as the javascript that made sense from the event model:
window.addEventListener( 'load', function() {
function listen( id ) {
var elem = document.getElementById( id )
elem.addEventListener( 'beginEvent', function() {
console.log( 'begin ' + id )
}, false )
elem.addEventListener( 'endEvent', function() {
console.log( 'end ' + id )
}, false )
}
listen( 'rect' )
listen( 'dx' )
})
But there's no events fired on either the rect or the animate in either browser. The next logical step seems to be to simulate the animation (ala. FakeSmile), but I want to use the browser's animation timer if at all possible.
RE your <set attributeName="value"> — you can't use SMIL to animate attributes of HTML elements, even if they're HTML elements embedded in SVG. (That would be a cool future extension, but its behavior isn't defined[1], so it would be a bit experimental at this point.)
RE the onbegin — yeah, Firefox doesn't fire animation events yet — that's yet-to-be-implemented.
[1] The SVG spec explicitly defines which SVG attributes and properties are animatable and which aren't. (see e.g. the "Animatable: " field below every attribute on w3.org/TR/SVG11/text.html) It does not define that for other languages (e.g. HTML), nor does HTML (because HTML doesn't have an animation component), so it's unclear which HTML attributes would be animatable in the first place.
If the textfield appears but the foreignObject element lacks a width and height attribute technically the browser that does that doesn't follow the SVG spec, since those attributes are required for the foreignObject to be visible inside the SVG.
Also the animations elements in SVG can be used on svg elements, but the SVG spec doesn't define if/how they apply to other types of markup.
An option if you want to use the animation timer might be to create an short repeating animation, e.g an animate element that animates some random attribute that isn't visible, and use the repeatEvent events as a trigger, calling back to a javascript function that modifies the html element(s).

Categories

Resources