I am new to React, and I am having trouble with some formatting issues.
I have one react component, an SVG that is the background element. On it, I am trying to render several points, with annotations when you hover.
I am able to get the points to show when I simply return the point directly, but I need to wrap it in a div so I can add some text.
Working, rendering the point in the right place:
class InfoPoint extends Component {
render() {
return (
<rect x="10" y="93" width="15" height="12" rx="2" ry="2" fill="red" ></rect>
);
}
}
Not working, I cannot find the element when I wrap it in the div:
class InfoPoint extends Component {
render() {
return (
<div className="point">
<rect x="10" y="93" width="15" height="12" rx="2" ry="2" fill="red" ></rect>
</div>
);
}
}
<rect> elements must be inside an <svg> element. And <div> elements are invalid inside a <svg> element. You can't indiscriminately mix HTML and SVG elements together.
Related
We use Umbraco for our customers, and we want to allow them to change icons for certain content.
It's possible to upload and select a custom SVG.
Because we work on a template base, we want to control the color of the icons.
Using javascript, we transform the <img> with an SVG to a HTML <svg>-tag.
That way, we can alter the fill of stroke in the SVG's using regular CSS.
But we ran into an issue, where a customer had uploaded multiple SVG icons which were all having internal CSS and using the same class-names for paths, circles and what not.
The problem is, the last SVG that's on the page overrules the CSS for all SVG's.
For example:
<svg ...>
<defs>
<style>.a{fill: red;}</style>
</defs>
<circle class="a" cx="50" cy="50" r="50" />
</svg>
<svg ...>
<defs>
<style>.a{fill: blue;}</style>
</defs>
<circle class="a" cx="50" cy="50" r="50" />
</svg>
The customer expects a red and a blue circle, because that's what he selected within the CMS.
But this renders to 2 blue circles.
Is there any way to keep CSS found in an SVG within that same SVG?
With a hint from #Lain, I have changed the to script.
It now add's a random-generated classname to the SVG, and prepends that classname to every class in the tag like this:
let styleTag = $svg.find('style');
if (styleTag.length) {
let svgClass = GenerateClassname(8);
const regex = /\.([a-z]{1})/ig; // Get every . followed by a letter
styleTag[0].innerHTML = styleTag[0].innerHTML.replace(regex, "." + svgClass +" .$1");
}
When you inject the svg into the html document, you can add a unique id to each svg and then use it in the css selector.
<svg id="svg-1">
<defs>
<style>#svg-1 .a{fill: red;}</style>
</defs>
<circle class="a" cx="50" cy="50" r="50" />
</svg>
<svg id="svg-2">
<defs>
<style>#svg-2 .a{fill: blue;}</style>
</defs>
<circle class="a" cx="50" cy="50" r="50" />
</svg>
And then, in your near future, you do want to style those individual elements with (global) styling.
shadowDOM (optional part of the native Web Components spec aka Custom Elements)
was created for use-cases like these
supported in all modern browsers
"outside/global" CSS does not leak in
"inside" CSS does not leak out
and inheritable styles, CSS properties and CSS :part can style shadowDOM
customElements.define("svg-circle", class extends HTMLElement{
connectedCallback(){
this.style.display = "inline-block";
this.style.width = "15vw";
this.attachShadow({mode:"open"})
.innerHTML = `<svg viewBox="0 0 100 100">
<style>.a { fill:${this.getAttribute("color")} }</style>
<circle class="a" cx="50" cy="50" r="45" />
<circle part="inner" class="a" cx="50" cy="50" r="20" />
</svg>`;
}
});
<style>
*::part(inner){ fill:rebeccapurple } /* higher Specificity */
</style>
<h2>Who is afraid of</h2>
<svg-circle color="red" ></svg-circle>
<svg-circle color="yellow" ></svg-circle>
<svg-circle color="blue" ></svg-circle>
I have an SVG icon:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="12" />
<path class="svg-fill"
d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm1,3.3,1.35-1a8,8,0,0,1,4.38,3.34L18.34,9,17,9.49,13,6.7Zm-3.35-1L11,5.3V6.7L7,9.49,5.66,9,5.27,7.69A8.1,8.1,0,0,1,9.65,4.35ZM7.08,17.11l-1.14.1A7.94,7.94,0,0,1,4,12c0-.12,0-.23,0-.35l1-.73,1.38.48,1.46,4.34Zm7.42,2.48a7.83,7.83,0,0,1-5,0L8.81,18.1,9.45,17h5.11l.64,1.11ZM14.27,15H9.73L8.38,11,12,8.44,15.63,11Zm3.79,2.21-1.14-.1-.79-1.37,1.46-4.34L19,10.93l1,.73c0,.11,0,.22,0,.34A7.94,7.94,0,0,1,18.06,17.21Z" />
</svg>
Which I'm trying to render on a D3 graph by using:
svg
.append('image')
.attr('xlink:href', MyIcon)
How can I change the color of the path in the svg dynamically, as I will be running the append code from a function something like:
const renderIcon = (iconColor) => {
svg
.append('image')
.attr('xlink:href', MyIcon)
// code to apply color param to the icon path
}
Couldn't find a nice way of actually getting the above working. The solution that I ended up finding was to instead define my icons in an defs element within an svg element in the return statement of my component:
return (
<svg>
<defs>
<MyIcon color={myColor1} id="myIcon1" />
<MyIcon color={myColor2} id="myIcon2" />
</defs>
</svg>
)
Where MyIcon would look something like:
<g fill={color} id={id}>
// other svg elements
</g>
And then use them by appending a use element to the svg element and passing in the icons id attribute:
svg.append('use').attr('href', '#myIcon1');
svg.append('use').attr('href', '#myIcon2');
I have many dynamically generated SVGs inside my HTML document. They contain (among other elements) some <text> elements, sort of like this:
<svg>
// bunch of other elements
<text x="50" y="25" text-anchor="middle" dominant-baseline="central" class="myclass">info 1</text>
<text x="50" y="75" text-anchor="middle" dominant-baseline="central" class="myclass">info 2</text>
// more other elements
</svg>
Is there anything like an id or name attribute that I can use for these two pieces of text, so that I can dynamically change them later? id is supposed to be unique in the whole document, and I have many of these SVGs. name appears to mean something different in SVG (as opposed to HTML forms), and in any case might be deprecated. I'm hesitant to use class, since this really isn't about styling and I don't want weird conflicts with other styles that might be present in the document. (I don't have control over the document my code is used in, or its stylesheets.) I also don't want to have to search the whole document for the element I'm looking for, since I'll already have a pointer to its parent. Ideally, I'd do something like this:
mySVG.getChildByType("type1").innerHTML = "here's new text";
(With the caveat that every similar SVG has one of these "type1"` children, that can be grabbed and messed with.)
Thanks.
I'm not sure why you think id is unusable. You say you are dynamically generating the SVGs. I presume that means at run-time. So just generate an id at the same time. Prefix it, if you need to, so that you can be sure it is going to be unique.
textElement.setAttribute("id", "my-prefix-" + nextTextElemIndex);
There's also nothing stopping you from adding your own custom attribute. Then style the elements using attribute selectors.
circle[foo="middle"] {
fill: green;
}
<svg viewBox="0 0 300 100">
<circle cx="50" cy="50" r="50" fill="red" foo="left"/>
<circle cx="150" cy="50" r="50" fill="red" foo="middle"/>
<circle cx="250" cy="50" r="50" fill="red" foo="right"/>
</svg>
It's not exactly clear what you want; it sounds like you want a way to get the text elements inside of a specific SVG?
In that case, use something like the following:
document.querySelectorAll("svg > text")[0].textContent = "I am the new string";
Now querySelectorAll() just uses CSS selectors, so you can replace svg in there with a .class, #id, or just use whatever CSS selector you want.
You could use rather verbose class names to avoid accidentally overridden styles inherited from a global css.
E.g a BEM-like naming scheme as commonly used in scripts/libs like image sliders, media players, map helpers.
If your svg text elements are dynamically compiled you might run a function adding index based IDs/class properties:
const parentSvgs = document.querySelectorAll('.mySvg');
setAutoIds(parentSvgs);
function setAutoIds(parents) {
parents.forEach(function(parent, p) {
let parentIdentifier = 'mySvg__parent--' + p;
parent.id = parentIdentifier;
parent.classList.add(parentIdentifier);
let children = parent.querySelectorAll('.mySvg__text');
children.forEach(function(child, c) {
let childIdentifier = `mySvg__text--${p}-${c}`;
child.id = childIdentifier;
child.classList.add(childIdentifier);
child.classList.add(`mySvg__text--${c}`);
})
})
}
//auto content example
setIndexedContent(parentSvgs);
function setIndexedContent(parents) {
parents.forEach(function(parent, p) {
let children = parent.querySelectorAll('.mySvg__text');
children.forEach(function(child, c) {
child.textContent = `svg nr.${p+1} info nr.${c+1}`;
});
});
}
// single text element selected by ID or class
document.getElementById('mySvg__text--1-3').textContent = 'unique text by ID';
document.getElementById('mySvg__text--1-2').textContent = 'unique text by Class';
.mySvg {
display: inline-block;
width: 20vw;
border: 1px dotted #ccc;
}
.mySvg__text {
font-size: 10px;
}
.mySvg__text--0 {
fill: orange
}
.mySvg__text--1 {
fill: green
}
.mySvg__text--2 {
fill: red
}
<svg class="mySvg" viewBox="0 0 100 100">
<text x="50" y="25" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="50" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="75" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
</svg>
<svg class="mySvg" viewBox="0 0 100 100">
<text x="50" y="10" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="20" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="30" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="40" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
</svg>
This way you could apply shared css styles but also select individual elements according to their global index.
I have a react component that has an SVG image. I have divided the SVG into multiple react box. I have query selector which selects all the react box and JS click event to auto click that react.
I tried working with both click and dispatch event. But none of them works in my scenario.
Below is the section of the code I am working on.
componentDidMount() {
var element = document.querySelectorAll("square");
for(var i = 0; i<element.length; i++) {
element[i].dispatchEvent(new Event('click'));
}
}
render(){
return (
<React.Fragment>
<div className="col-12">
<svg viewBox="0 0 100 100">
<image xlinkHref={imageFile} height="100%" width="100%" />
<g><rect className="square" x="10" y="10" width="20" height="10" fillOpacity=".2" onClick={() =>console.log("clicked")}></rect> </g>
<g><rect className="square" x="30" y="10" width="20" height="10" fillOpacity=".4" onClick={() =>console.log("clicked")}></rect> </g>
</svg>
</div>
</React.Fragment>
)
}
I also tried using the click() function and did not work for SVG images and also is there a way we could automate a click in each square every 10 seconds?
You forgot a . in the query selector so the node list was actually empty.
If you want to automate a click in each square every 10 seconds, this code does the trick:
const elements = document.querySelectorAll(".square");
const intervalsIdentifiers = Array.from(elements).map(x => setInterval(() => x.dispatchEvent(new Event('click')), 10000));
The dispatchEvent method is indeed the only way, because the rect element doesn't have a click method (only HTML elements do, not SVG elements) as demonstrated below:
console.log('click' in SVGRectElement.prototype); // false
console.log(HTMLElement.prototype.hasOwnProperty('click')); // true
console.log(HTMLButtonElement.prototype instanceof HTMLElement); // true (a button has the click method)
console.log(SVGRectElement.prototype instanceof HTMLElement); // false
The full working code (native JavaScript but should work as well with React in the componentDidMount hook):
const elements = document.querySelectorAll(".square");
const intervalsIdentifiers = Array.from(elements).map(x => setInterval(() => x.dispatchEvent(new Event('click')), 10000));
<div className="col-12">
<svg viewBox="0 0 100 100">
<image xlink:Href="https://img-19.ccm2.net/8vUCl8TXZfwTt7zAOkBkuDRHiT8=/1240x/smart/b829396acc244fd484c5ddcdcb2b08f3/ccmcms-commentcamarche/20494859.jpg" height="100%" width="100%" />
<g><rect class="square" x="10" y="10" width="20" height="10" fillOpacity=".2" onclick="console.log('clicked')"></rect> </g>
<g><rect class="square" x="30" y="10" width="20" height="10" fillOpacity=".4" onclick="console.log('clicked')"></rect> </g>
</svg>
</div>
I have created numerous polygon shapes in SVG format. and grouped them together.
When the user hovers over the group a tooltip box appear. I have used ecmascript.
What i am looking to do is make the tooltip box a multiline box.
Any ideas how to do this?
<script type="text/ecmascript">
<![CDATA[
function init(evt)
{
if ( window.svgDocument == null )
{
svgDocument = evt.target.ownerDocument;
}
tooltip = svgDocument.getElementById('tooltip');
tooltip_bg = svgDocument.getElementById('tooltip_bg');
}
function ShowTooltip(evt, mouseovertext)
{
tooltip.setAttributeNS(null,"x",evt.clientX+17);
tooltip.setAttributeNS(null,"y",evt.clientY+14);
tooltip.firstChild.data = mouseovertext;
tooltip.setAttributeNS(null,"visibility","visible");
length = tooltip.getComputedTextLength();
tooltip_bg.setAttributeNS(null,"width",length+8);
tooltip_bg.setAttributeNS(null,"x",evt.clientX+14);
tooltip_bg.setAttributeNS(null,"y",evt.clientY+1);
tooltip_bg.setAttributeNS(null,"visibility","visibile");
}
function HideTooltip(evt)
{
tooltip.setAttributeNS(null,"visibility","hidden");
tooltip_bg.setAttributeNS(null,"visibility","hidden");
}
]]>
</script>
<SVG>
<g onmousemove="ShowTooltip(evt, 'GHANA 2000')" onmouseout="HideTooltip(evt)">
<path fill="#EEEEEE" d="M250,0c47,0,85.183,10.506,125,33.494L250,250V0z"/>
<path id="score" d="M250,57c36.284,0,65.761,8.11,96.5,25.857L250,250V57z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="2" stroke-miterlimit="10" d="M250,0c47,0,85.183,10.506,125,33.494L250,250V0z"/>
<text transform="matrix(1 0 0 1 283.9883 92.0024)" fill="#FFFFFF" font-family="'WalkwayBlack'" font-size="16">62</text>
</g>
<rect class="tooltip_bg" id="tooltip_bg" x="0" y="0" width="55" height="17" visibility="hidden"/>
<text class="tooltip" id="tooltip" x="0" y="0" visibility="hidden">Tooltip</text>
<SVG>
You could create more <text> elements or <tspan> elements within the existing text element, put subsequent lines of text in the additional elements and then position each text/tspan below the previous ones by giving them the same x attribute value and increase the y attribute by the height of the bounding box of the previous line.
Alternatively and more simply, just create <title> elements as children of the polygon shape elements and put the multiline text directly within that and the tooltips will be shown by most UAs as multiline tooltips, it certainly works in Firefox and Opera. Here's a html example but it should work just as well with SVG except that SVG has a title element rather than a title attribute.