Output html on a data element - javascript

So I've the following function which modifies an element from openlayers.
As you can see on the documentation the property label accepts either html or string
methods: {
onUpdatePosition (coordinate) {
this.deviceCoordinate = coordinate
this.$refs.map.$map.getControls().extend([
new ZoomToExtent({
label: `<svg style="width:24px;height:24px" viewBox="0 0 24 24">
<path fill="currentColor" d="M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M3.05,13H1V11H3.05C3.5,6.83 6.83,3.5 11,3.05V1H13V3.05C17.17,3.5 20.5,6.83 20.95,11H23V13H20.95C20.5,17.17 17.17,20.5 13,20.95V23H11V20.95C6.83,20.5 3.5,17.17 3.05,13M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z" />
</svg>`,
})
])
},
}
And of course the output is a plain text, how can I output the actual html there?

The label can be an HTMLElement, not a string containing HTML code.
Try
var myLabelHTML = document.createElement('span');
myLabelHTML.innerHTML = '<svg style="width:24px;height:24px" viewBox="0 0 24 24">
<path fill="currentColor" d="M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M3.05,13H1V11H3.05C3.5,6.83 6.83,3.5 11,3.05V1H13V3.05C17.17,3.5 20.5,6.83 20.95,11H23V13H20.95C20.5,17.17 17.17,20.5 13,20.95V23H11V20.95C6.83,20.5 3.5,17.17 3.05,13M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z" />
</svg>';
//...
new ZoomToExtent({
label:myLabelHTML;
});

Related

Merge multiple SVG elements into one SVG and download it with React

I have a logo creator in my app.
User has to type smthn in input field (for example "TESTING") and then it renders in preview block.
Every letter in preview is the svg element:
(Each letter can be a glyph letter or regular letter, it means that there are 2 different fonts)
const C = ({ glyph }) => {
if (!glyph) {
return (
<svg width="93" height="132" viewBox="0 0 93 132" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M75 41.76L87.6 34.92C82.2 21.6 67.2 10.8 49.08 10.8C19.92 10.8 4.8 30.12 4.8 54C4.8 77.88 19.2 97.2 49.08 97.2C67.2 97.2 82.2 86.4 87.6 73.08L75 66.12C71.04 75.72 61.92 84 48.48 84C30.72 84 19.68 71.76 19.68 54C19.68 36.24 30.72 24 48.48 24C61.92 24 71.04 32.28 75 41.76Z"
fill="currentColor"
/>
</svg>
);
} else {
return (
<svg width="96" height="132" viewBox="0 0 96 132" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M20.4 51.24H33.6C33.6 31.92 42.24 24 54.36 24C64.92 24 72.36 29.16 76.2 40.56L88.8 33.6C83.4 19.2 71.64 10.8 54.48 10.8C32.04 10.8 18.72 25.08 18.72 51.24H4.8C4.8 80.52 24.48 97.08 49.8 97.08C70.68 97.08 83.04 85.8 89.16 74.04L77.64 67.44C71.88 77.16 64.2 83.64 50.4 83.64C33.12 83.64 21.12 71.16 20.4 51.24Z"
fill="currentColor"
/>
</svg>
);
}
};
I change glyph state by clicking on letter (this is a hover view):
How it looks in devtools:
What i expect:
On download click:
I need to download this logo ("TESTING") as .svg file.
const svgHtml = Array.from(logoRef.current.querySelectorAll('span'))
.map((el) => el.innerHTML)
.join('\n');
const svg = `
<svg xmlns="http://www.w3.org/2000/svg">
${svgHtml}
</svg>
`;
const blob = new Blob([svg], { type: 'image/svg+xml' });
createDownloadLink(blob, `${inputText}.svg`);
Actual behavior:
All letters have no relative positioning as in preview:
Do you have any ideas how can i do this?
Or maybe there is a better solution you can purpose?
Thanks in advance
You need to calculate x offset values for each letter svg before merging them, since they have no idea how they are positioned in the HTML DOM context (i.e as children of <span> elements).
This offset value would be incremented by the viewBox width
(3. viewBox argument: viewBox="0 0 93 132" => width=93):
JavaScript example
let spans = document.querySelectorAll('span');
let svgMarkup = '';
let xOffset = 0;
let viewBoxHeight = 132;
let letterSpacing = 5;
spans.forEach((span,i)=>{
let svg = span.querySelector('svg');
let style = window.getComputedStyle(span)
let color = style.color;
let viewBox = svg.getAttribute('viewBox').split(' ');
let width = +viewBox[2];
let glyph = svg.querySelector('path');
// clone path to apply offset
let glyphCloned = glyph.cloneNode(true);
// apply x offset by translate()
glyphCloned.setAttribute('transform', `translate(${xOffset} 0)`);
glyphCloned.style.color=color;
let glyphMarkup = glyphCloned.outerHTML;
glyphCloned.remove();
svgMarkup+=glyphMarkup;
// increment offset for next letter
xOffset += i==spans.length-1 ? width : width+letterSpacing;
})
let svgCombined = `
<svg id="combined" xmlns="http://www.w3.org/2000/svg" width="${xOffset}" height="${viewBoxHeight}" viewBox="0 0 ${xOffset} ${viewBoxHeight}">
${svgMarkup}
</svg>`;
document.body.insertAdjacentHTML('beforeend', svgCombined);
svg{
height:10em;
width:auto;
border:1px solid #ccc;
}
.blue{
color:blue
}
<span><svg width="93" height="132" viewBox="0 0 93 132" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M75 41.76L87.6 34.92C82.2 21.6 67.2 10.8 49.08 10.8C19.92 10.8 4.8 30.12 4.8 54C4.8 77.88 19.2 97.2 49.08 97.2C67.2 97.2 82.2 86.4 87.6 73.08L75 66.12C71.04 75.72 61.92 84 48.48 84C30.72 84 19.68 71.76 19.68 54C19.68 36.24 30.72 24 48.48 24C61.92 24 71.04 32.28 75 41.76Z" fill="currentColor" />
</svg>
</span>
<span class="blue">
<svg width="96" height="132" viewBox="0 0 96 132" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.4 51.24H33.6C33.6 31.92 42.24 24 54.36 24C64.92 24 72.36 29.16 76.2 40.56L88.8 33.6C83.4 19.2 71.64 10.8 54.48 10.8C32.04 10.8 18.72 25.08 18.72 51.24H4.8C4.8 80.52 24.48 97.08 49.8 97.08C70.68 97.08 83.04 85.8 89.16 74.04L77.64 67.44C71.88 77.16 64.2 83.64 50.4 83.64C33.12 83.64 21.12 71.16 20.4 51.24Z" fill="currentColor" />
</svg>
</span>
<span><svg width="93" height="132" viewBox="0 0 93 132" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M75 41.76L87.6 34.92C82.2 21.6 67.2 10.8 49.08 10.8C19.92 10.8 4.8 30.12 4.8 54C4.8 77.88 19.2 97.2 49.08 97.2C67.2 97.2 82.2 86.4 87.6 73.08L75 66.12C71.04 75.72 61.92 84 48.48 84C30.72 84 19.68 71.76 19.68 54C19.68 36.24 30.72 24 48.48 24C61.92 24 71.04 32.28 75 41.76Z" fill="currentColor" />
</svg>
</span>
<p>Combined Svg</p>
The example above will also select only the <path> elements instead of their parent <svg>s.
In fact, you could also set an offset via x attribute.
However, a lot of graphic applications struggle with nested svgs - so copying only paths is usually a more robust solution for standalone svgs.
Besides, we're adding a viewBox based to the total width and height of all combined letter svgs.

How to render n slices inside svg circle?

I am developing a reactJS app.
I need to render an svg circle and when I click it it spawns n equal slices inside.
I created the slices, here is the code
renderSlices = () => {
let slices = [];
const numberOfSlice = 12; //number of slices
for (let i = 0; i < numberOfSlice; i++) {
slices.push({ percent: 1 / numberOfSlice, color: 'gray' });
}
let cumulativePercent = 0;
let arr = [];
arr = slices.map(slice => {
const [startX, startY] = this.getCoordinatesForPercent(cumulativePercent.toString());
cumulativePercent += slice.percent;
const [endX, endY] = this.getCoordinatesForPercent(cumulativePercent.toString());
const largeArcFlag = slice.percent > 0.5 ? 1 : 0;
const pathData = [
`M ${startX} ${startY}`, // Move
`A 1 1 0 ${largeArcFlag} 1 ${endX} ${endY}`, // Arc
'L 0 0', // Line
].join(' ');
return <path d={pathData} fill={slice.color} key={pathData} />;
});
return arr;
}
getCoordinatesForPercent(percent: string) {
const x = Math.cos(2 * Math.PI * parseFloat(percent));
const y = Math.sin(2 * Math.PI * parseFloat(percent));
return [x, y];
}
Render method:
<div className="container">
<svg
height="306"
width="306"
viewBox="-1 -1 2 2"
>
{/* <circle cx="150" cy="150" r="148" stroke="black"
strokeWidth="2" fill={"transparent"}/> */}
{this.renderSlices()}
</svg>
</div>
The problem is when I remove the comment from the circle tag and I remove the viewBox, only the circle show up, and when I comment the circle tag and put the viewBox, only the slices show up.
I would like to have the circle with a visible stroke and inside it the slices.
Any help please ?
EDIT:
<svg height="306" width="306" viewBox="0 0 306 306">
<path d="M 1 0 A 1 1 0 0 1 0.8660254037844387 0.49999999999999994 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 0.8660254037844387 0.49999999999999994 A 1 1 0 0 1 0.5000000000000001 0.8660254037844386 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 0.5000000000000001 0.8660254037844386 A 1 1 0 0 1 6.123233995736766e-17 1 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 6.123233995736766e-17 1 A 1 1 0 0 1 -0.4999999999999998 0.8660254037844387 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -0.4999999999999998 0.8660254037844387 A 1 1 0 0 1 -0.8660254037844385 0.5000000000000003 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -0.8660254037844385 0.5000000000000003 A 1 1 0 0 1 -1 5.66553889764798e-16 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -1 5.66553889764798e-16 A 1 1 0 0 1 -0.866025403784439 -0.4999999999999994 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -0.866025403784439 -0.4999999999999994 A 1 1 0 0 1 -0.5000000000000004 -0.8660254037844385 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -0.5000000000000004 -0.8660254037844385 A 1 1 0 0 1 -1.8369701987210297e-16 -1 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M -1.8369701987210297e-16 -1 A 1 1 0 0 1 0.5000000000000001 -0.8660254037844386 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 0.5000000000000001 -0.8660254037844386 A 1 1 0 0 1 0.8660254037844388 -0.49999999999999967 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
<path d="M 0.8660254037844388 -0.49999999999999967 A 1 1 0 0 1 1 -2.4492935982947064e-16 L 0 0" stroke-width="2" stroke="black" fill="gray"></path>
</svg>
getCoordinatesForPercent(percent: string, radius: number, circle: {x: number, y: number}) {
const x = radius * Math.cos(2 * Math.PI * parseFloat(percent)) + circle.x;
const y = radius * Math.sin(2 * Math.PI * parseFloat(percent)) + circle.y;
return [x, y];
}
Multiply with radius and add the circle coordinates
and change A 1 1 0 ${largeArcFlag} to A ${radius} ${radius} 0 ${largeArcFlag} and 'L 0 0' to 'L ${circle.x} ${circle.y}'

Why <marker> doesn't orientate as the <path>

I'm trying to create a curved arrow with svg. I'm using d3.line() to generate the path.
let points = [
[400,100],
[450,200],
[350,200],
[385,275]
]
let path = d3.line().curve(d3.curveCardinal)(points)
console.log(path)
// -> M400,100C400,100,458.3333333333333,183.33333333333334,450,200C441.6666666666667,216.66666666666666,360.8333333333333,187.5,350,200C339.1666666666667,212.5,385,275,385,275
But when I try to use this result in a svg:
<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<path d="M400,100C400,100,458.3333333333333,183.33333333333334,450,200C441.6666666666667,216.66666666666666,360.8333333333333,187.5,350,200C339.1666666666667,212.5,385,275,385,275"
stroke-width="2" stroke="lightblue" fill="none" style="marker-end: url(#Triangle);"></path>
</svg>
And here is the SVG result
.
I can't figure out why the marker doesn't orientate. Is there a better library to generate path to resolve this?
That's the expected behaviour. The issue is that in a cardinal spline...
Two additional points are required on either end of the curve.
And those points seem to interfere with the marker orientation (which is indeed the case, see LeBeau's answer).
You can easily see this if you change the curve. For instance, using curveBasis:
let points = [
[400,100],
[450,200],
[350,200],
[385,275]
]
let path = d3.line().curve(d3.curveBasis)(points)
d3.select("#myPath").attr("d", path);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<path id="myPath" stroke-width="2" stroke="lightblue" fill="none" style="marker-end: url(#Triangle);"></path>
</svg>
In your case, a solution (arguably a hack) may be adding a final line to the path, just 1px away from the final point:
path = path + "L387,277";
Here is the demo:
let points = [
[400,100],
[450,200],
[350,200],
[385,275]
]
let path = d3.line().curve(d3.curveCardinal)(points)
path = path + "L387,277";
d3.select("#myPath").attr("d", path);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<path id="myPath" stroke-width="2" stroke="lightblue" fill="none" style="marker-end: url(#Triangle);"></path>
</svg>
This is because the last control point and the end-point of your path have the same coordinates: (385,275).
SVG uses the control point vector to work out what the curve direction is at that point. If your control point vector is from (385,275) to (385,275), then it can't determine the angle. So it defaults to an angle of 0 degrees.
First, the ref attributes are sort of correct but can be better I think, make the refX 0 since you using the full viewBox.
I think the marker's orientation is correct and updated. But based on the ending of the path, the interpolation of the orientation might look incorrect. So you can verify this behavior by cutting your pathstring from the last C... curve and will see that the orientation is correct.
I further tested it to see if it is correct, at least for line segments, here is a fiddle and i didn't even use d3:
https://jsfiddle.net/ibowankenobi/L8x19rco/2/
var path = document.querySelector("path[stroke]");
var arr = Array.apply(null,Array(path.getTotalLength()/4 << 0)).map(function(d,i){
var p = this.getPointAtLength(i*4);
return [p.x,p.y];
},path);
var length = arr.length;
animate();
function animate(index){
if(index >= length){
return;
}
var index = index || 0;
path.setAttribute("d","M"+arr.slice(1,Math.min(++index+1,length)).join("L"));
window.requestAnimationFrame(function(){animate(index);});
}

Looping through elements is not working

I am trying to add an extra path to each SVG on a webpage. I set up my for loop and it is working, but once it loops through it tells me appendChild() is not a function. If I take everything out of the loop appendChild() works. What am I doing incorrectly here?
var svg = document.getElementsByClassName('curve-position-bottom'); //Get svg element
for (var i = 0; i < svg.length; i++) {
console.log(svg.length);
function addPath() {
console.log('working');
var newElement = document.createElementNS("http://www.w3.org/2000/svg", 'path'); //Create a path in SVG's namespace
newElement.setAttribute("d", "M0 0 C50 100 50 100 100 0 "); //Set path's data
newElement.style.stroke = "#005780"; //stroke colour
newElement.style.strokeWidth = "13px"; //Set stroke width
newElement.style.fill = "none"; //Set stroke width
svg.appendChild(newElement);
}
addPath();
}
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 100 100" version="1.1" preserveAspectRatio="none" height="45px" class="engle-curve curve-position-bottom" style="fill:#e6e2af">
<path stroke-width="0" d="M0 0 C50 100 50 100 100 0 L100 100 0 100"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 100 100" version="1.1" preserveAspectRatio="none" height="45px" class="engle-curve curve-position-bottom" style="fill:#e6e2af">
<path stroke-width="0" d="M0 0 C50 100 50 100 100 0 L100 100 0 100"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 100 100" version="1.1" preserveAspectRatio="none" height="45px" class="engle-curve curve-position-bottom" style="fill:#e6e2af">
<path stroke-width="0" d="M0 0 C50 100 50 100 100 0 L100 100 0 100"></path>
</svg>
The return value of document.getElementsByClassName is a NodeList, not an individual element. To use your defined SVG, you'll need to use svg[i].
Also, unrelated to your question, but it would be a good idea to move that function definition out of the loop (for performance and scoping reasons) and to call it with an SVG element as a parameter. It would look more like this:
var svg = document.getElementsByClassName('curve-position-bottom'); //Get svg elements
function addPath(svg) {
console.log('working');
var newElement = document.createElementNS("http://www.w3.org/2000/svg", 'path'); //Create a path in SVG's namespace
newElement.setAttribute("d", "M0 0 C50 100 50 100 100 0 "); //Set path's data
newElement.style.stroke = "#005780"; //stroke colour
newElement.style.strokeWidth = "13px"; //Set stroke width
newElement.style.fill = "none"; //Set stroke width
svg.appendChild(newElement);
}
for (var i = 0, length = svg.length; i < length; i++) {
addPath(svg[i]);
}
Instead
svg.appendChild(newElement);
try:
svg[i].appendChild(newElement);

jQuery/Svg : increment value "dur" when key is pressed

I would like to increase the speed of my element #Object when I press a key.
The HTML code:
<path d="M 0,70 A 65,70 0 0,0 65,0 5,5 0 0,1 75,0 75,70 0 0,1 0,70Z" fill="#FF6600">
<animateTransform id="object" attributeName="transform" type="rotate" from="360 0 0" to="0 0 0" dur="3s" repeatCount="indefinite" />
</path>
The Script:
window.addEventListener("keydown", checkKeyPressed, false);
function checkKeyPressed(e) {
if (e.keyCode == "38") {
var getTheSpeed = parseInt(document.getElementById("object").dur, 10);
getTheSpeed = isNaN(getTheSpeed) ? 3 : getTheSpeed;
getTheSpeed++;
document.getElementById("object").dur = getTheSpeed;
}
}
My problem is that dur doesn't only take a number value. The seconds "s" must be specified. So, I cannot actually use isNaN property :(
I'm a beginner in JavaScript ^^'
Anyone have a solution to run this script?
Ty <3
change the following lines
var getTheSpeed = parseInt(document.getElementById("object").getAttribute("dur").slice(0,-1), 10);
(this will remove the s from the current value of "dur" using slice(0,-1))
and
document.getElementById("object").setAttribute("dur",String(getTheSpeed) + "s");
(this will set the new incremented value with an s added to it using string concatenation + "s")
Check this out--hope it helps^^
window.addEventListener("keydown", checkKeyPressed, false);
function checkKeyPressed(e) {
if (e.keyCode == "38") {
var getTheSpeed = parseInt(document.getElementById("object").getAttribute("dur").slice(0, -1), 10);
getTheSpeed = isNaN(getTheSpeed) ? 3 : getTheSpeed;
getTheSpeed++;
document.getElementById("object").setAttribute("dur", String(getTheSpeed) + "s");
alert(document.getElementById("object").getAttribute("dur"));
}
}
<path d="M 0,70 A 65,70 0 0,0 65,0 5,5 0 0,1 75,0 75,70 0 0,1 0,70Z" fill="#FF6600">
<animateTransform id="object" attributeName="transform" type="rotate" from="360 0 0" to="0 0 0" dur="3s" repeatCount="indefinite" />
</path>

Categories