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

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>

Related

Rounded corners in SVG path semi circle

I have a path which is a semi-circle. How do I make the corners rounded? I do not want to use stroke-linecap: round as I need it to be rounded only on these corners:
<svg>
<g>
<!--background -->
<path fill="none" stroke-dasharray="" stroke-width="16" stroke="#607985" d="M30 100 A 40 40 0 0 1 170 100"></path>
<!-- strokes -->
<path id="meter-back" fill="none" stroke-width="15" stroke="white" d="M30 100 A 40 40 0 0 1 170 100"></path>
<!--progress -->
<path id="meter-fill" fill="none" stroke-dashoffset="219.942" stroke-dasharray="109.971, 109.971" stroke="rgba(96,121,133,0.7)" stroke-width="15" d="M30 100 A 40 40 0 0 1 170 100" stroke="#607985"></path>
</g>
</svg>
Here is a fixed solution. dividerPos can be in range from 0 to 1:
const getPath = (outerRadius, innerRadius, cornerRadius, dividerPos) => {
const angle = Math.PI * (1 - dividerPos);
const outerPointX = outerRadius * Math.cos(angle);
const outerPointY = outerRadius * -Math.sin(angle);
const innerPointX = innerRadius * Math.cos(angle);
const innerPointY = innerRadius * -Math.sin(angle);
const left = `M ${-outerRadius},0
A ${outerRadius},${outerRadius} 0 0 1
${outerPointX},${outerPointY}
L ${innerPointX},${innerPointY}
A ${innerRadius},${innerRadius} 0 0 0 ${-innerRadius},0
Q ${-innerRadius},${cornerRadius}
${-innerRadius-cornerRadius},${cornerRadius}
H ${-outerRadius+cornerRadius}
Q ${-outerRadius},${cornerRadius}
${-outerRadius},0
Z`;
const right = `M ${outerPointX},${outerPointY}
A ${outerRadius},${outerRadius} 0 0 1
${outerRadius},0
Q ${outerRadius},${cornerRadius}
${outerRadius-cornerRadius},${cornerRadius}
H ${innerRadius+cornerRadius}
Q ${innerRadius},${cornerRadius}
${innerRadius},0
A ${innerRadius},${innerRadius} 0 0 0
${innerPointX},${innerPointY}
Z`;
return {left, right};
};
const {left, right} = getPath(120, 90, 15, 0.5);
d3.select('.left').attr('d', left);
d3.select('.right').attr('d', right);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width='300' height='200'>
<g transform='translate(150,150)'>
<path stroke='grey' fill='grey' class='left'/>
<path stroke='grey' fill='none' class='right'/>
</g>
</svg>
Use getPath routine to compute the desired path
(The 0,0 point in the center of the semi-circle):
const getPath = (outerRadius, innerRadius, cornerRadius) => {
return `M ${-outerRadius},0
A ${outerRadius},${outerRadius} 1 1 1 ${outerRadius},0
Q ${outerRadius},${cornerRadius}
${outerRadius-cornerRadius},${cornerRadius}
H ${innerRadius+cornerRadius}
Q ${innerRadius},${cornerRadius}
${innerRadius},0
A ${innerRadius},${innerRadius} 0 0 0
${-innerRadius},0
Q ${-innerRadius},${cornerRadius}
${-innerRadius-cornerRadius},${cornerRadius}
H ${-outerRadius+cornerRadius}
Q ${-outerRadius},${cornerRadius}
${-outerRadius},0
Z`;
};
d3.select('path').attr('d', getPath(120, 90, 12));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width='300' height='200'>
<g transform='translate(150,150)'>
<path stroke='red' fill='none'/>
<circle fill='red' r='5' cx='0' cy='0'/>
</g>
</svg>

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}'

Output html on a data element

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;
});

Change SVG fill in a loop

I have simple SVG illustration, is there is any way to change it's color constantly ? like a loop non-stop random color change.
here is my svg
<svg width="533" height="499" viewBox="0 0 533 499" fill="none" xmlns="http://www.w3.org/2000/svg">
This is how I would do it: I'm using colors hsl for the fill and I'm animating the hue of the colors using requestAnimationFrame. I hope it helps.
let p1 = document.querySelectorAll("path")[0];
let p2 = document.querySelectorAll("path")[1]
let h = 0;
function changeColor(){
window.requestAnimationFrame(changeColor);
h+=.5;
h2=210+h;
p1.setAttributeNS(null,"fill", `hsl(${~~h},100%,50%)`);
p2.setAttributeNS(null,"fill", `hsl(${~~h2},100%,50%)`);
}
changeColor()
<svg width="533" height="499" viewBox="0 0 533 499" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M258.089 59.6035C309.803 -3.94652 379.363 78.1818 407.679 127.19C352.338 67.4782 301.718 129.7 287.076 167.787C272.435 205.874 233.694 210.043 205.199 217.679C187.359 222.459 146.446 248.26 128.6 264.085C109.864 289.466 48.3081 292.846 41.8378 268.698C27.0852 213.64 95.5238 148.37 137.644 123.97C163.705 101.458 206.375 123.154 258.089 59.6035Z" fill="blue"/>
<path d="M448.323 394.788C427.389 384.661 420.75 356.279 420.047 343.354C441.009 284.421 527.63 350.762 528.167 368.218C528.703 385.674 474.491 407.447 448.323 394.788Z" fill="red"/>
</svg>
Select the element and recursively call a function that sets the fill attribute of the SVG element you want to recolor with a random hex.
const recolor = element => {
const randomColor = '#'+Math.floor(Math.random()*16777215).toString(16)
circle.setAttribute('fill', randomColor)
setTimeout(() => recolor(element), 600)
}
recolor(document.querySelector('#circle'))
svg circle {
transition: fill .5s linear;
}
<svg height="100" width="100">
<circle id="circle" cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>
fill on <svg> doesnot work. Its for its elements. You can change its background
function random_rgba() {
var o = Math.round, r = Math.random, s = 255;
return 'rgba(' + o(r()*s) + ',' + o(r()*s) + ',' + o(r()*s) + ',' + r().toFixed(1) + ')';
}
function changeColor(){
document.querySelector('svg').style.background = random_rgba();
}
setInterval(changeColor,200);
<svg width="533" height="499" viewBox="0 0 533 499" fill="none" xmlns="http://www.w3.org/2000/svg">

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);});
}

Categories