Inspiring myself from this answer, I want to make several progress circle in my page (about 30). It works perfectly for one but I don't get the circle for the values. I however get the percentage displayed correctly.
I've tried various thing, adding [count] to most of the options but still the circle is not drawn for each cell.
I added my code in this Fiddle.
Can you see what's wrong?
Your drawCircle function needs a bit more information (ctx and radius)
var drawCircle = function(ctx, radius, color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth;
ctx.stroke();
};
and you need to pass it that info when using it:
drawCircle(ctx[count], radius[count], '#efefef', options[count].lineWidth, 100 / 100);
drawCircle(ctx[count], radius[count], color[count], options[count].lineWidth, options[count].percent / 100);
like here: https://fiddle.jshell.net/6ooL53pp/3/
Related
I try to create a countdown clock using canvas and useEffect. It work fine until I test it in iPhone's browser (all device >= iPhone 8). When my clock running on iPhone 8, sometimes it has issue look like in picture.
This issue don't happend in other device (android, laptop, iphone <= 6)
Please help me!
[![`
useEffect(() => {
console.log('percent canvas = ', percent);
// var canvas = document.getElementById("clockCanvas");
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, width, width);
ctx.lineWidth = strokeWidth;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.fillStyle = backgroundClock;
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.strokeStyle = colorStroke;
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(cx, cy, radius, -0.5 * Math.PI + (2 * (percent / 100) * Math.PI), - 0.5 * Math.PI, true);
ctx.strokeStyle = strokeColor;
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = textStrokeWidth;
ctx.strokeStyle = textStrokeColor;
ctx.font = `${textSize}px Roboto`;
ctx.textAlign = "center";
ctx.textBaseline = 'middle';
ctx.strokeText(text, width / 2, width / 2, width - 2 * strokeWidth);
ctx.closePath();
},
[
cx,
cy,
radius,
width,
percent,
strokeColor,
strokeWidth,
text,
textSize,
textStrokeColor,
textStrokeWidth
]);
return (
<canvas id="clockCanvas" width={width} height={width} ref={canvasRef}>
</canvas>
);
};
export default Clock;
`]2]2
Hey there Do Anh Bon!
I know this question is 8 months old, so I'm assuming that you found a solution, but in case you did not...
I am working on a web development project and encountered a similar error. I spent quite some time doing research on the topic and I believed that I had tried nearly everything (changing image rendering settings in CSS, messing with device pixel ratio in various ways, etc.). None of the suggestions even came close to fixing the problem. Some of them actually made the code more verbose and the resulting image less appealing on top of that.
What I finally found to be the solution was to simply use the close path function INSTEAD of drawing the last iteration (meaning the last line) of my drawing. What this does is simply connect the line to the starting position.
function drawShape(x, y, radius, inset, n) {
ctx.fillStyle = "hsl(" + hue + ",100%,50%)";
ctx.beginPath();
ctx.save();
ctx.translate(x, y)
ctx.moveTo(0, -radius);
for (let i = 0; i < n; i++) {
ctx.rotate(Math.PI / n);
ctx.lineTo(0, -radius * inset);
ctx.rotate(Math.PI / n);
if (i === n - 1) {
break;
} else {
ctx.lineTo(0, -radius);
}
}
ctx.restore();
ctx.closePath();
ctx.stroke();
ctx.fill();
}
Keep in mind that since you are attempting to draw a curved line, this solution may not work for you (I have not tried it out with your code).
If this solution does not work, then another thing you might want to try to to omit the use of close path and see what results you get then.
I really hope this helps!
-Keith
Before
After
I'm using CSS to try and create a label (which is a popup that always remains on the map) attached to a circle. The following link will lead to the image of what I'm trying to do: Image. In order to achieve this I've been using the following code:
$(popup._container.firstChild).css({
background: "-webkit-radial-gradient(-29px" + percentZoom + ", circle closest-corner, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 0) 58px, white 59px)"
});
Before, I was calculating the percentZoom depending on the radius of the circle and the zoom where the map is now.
var percent = (50 * presentCircleRadius) / 300000 //when the radius is 300000 the percentage should be 50%
var percentZoom = (percent * zoom) / 6; // then calculate it the exact zoom that should be used depending on the zoom. Being 6 the default one.
This didn't work or it had many issues when I zoomed in on the map (considering that the circle doesn't really change but the curvature seems to becoming flatter).
I tried using canvas as well to get the result that I wanted it, but I had issues. I was using two arches to build the top part and the bottom part, then thought about using two rectangles to create the two parts to the right of the circle. The problem with this it's that the circle is transparent and it's meant to start on the edge of it, if I used this solution the rectangle would appear in the middle of the circle.
var canvas = document.getElementById('myCanvas1');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = 1.1 * Math.PI;
var endAngle = 1.9 * Math.PI;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, 1.6 * Math.PI, 0 * Math.PI, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = 'black';
context.stroke();
context.beginPath();
context.arc(x, y, radius, 0 * Math.PI, 0.4 * Math.PI, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = 'red';
context.stroke();
context.beginPath();
context.lineWidth = "10";
context.strokeStyle = "blue";
context.rect(x, y - radius, 150, radius);
context.stroke();
<canvas id="myCanvas1" width="578" height="250"></canvas>
So I thought of using lines instead of rectangles to create the right part of the label: fiddle, the problem with this solution is, as mention before, as you zoom the curvature will change and I found no way to calculate exactly where the lines on the top and on the bottom should start.
Is there a way to do what I want to do: Make it so that the label follows the curvature of the circle as you zoom in and out and if so how can I make it so considering that there might be more than one circle per zoom with different radius?
I'm trying to learn how to draw/fill different shapes by using canvas and JavaScript, but my shapes doesn't get filled in the way I want them to, at all. The body of my HTML-document is this simple line:
<canvas id="canvas1" width="500" height="500"></canvas>
And my JavaScript-file looks like this:
function draw() {
var canvas1 = document.getElementById('canvas1');
if(canvas1.getContext) {
var ctx = canvas1.getContext('2d');
var gradient = ctx.createLinearGradient(0, 0, 50, 0);
gradient.addColorStop(0, "blue");
gradient.addColorStop(1, "white");
ctx.beginPath();
ctx.moveTo(25,25);
ctx.lineTo(100, 25);
ctx.stroke();
ctx.moveTo(25, 50);
ctx.bezierCurveTo(25, 50, 50, 80, 75, 60)
ctx.fillStyle = "black";
ctx.fill();
ctx.beginPath();
ctx.moveTo(75, 100);
ctx.arc(50, 100, 25, 0, Math.PI*2, true);
ctx.fillStyle = "black";
ctx.fill();
ctx.beginPath();
ctx.fillStyle = gradient;
ctx.arc(75, 150, 25, 0, Math.PI*2, true);
ctx.fill();
}
}
But this is the result:
And I don't get it. I've tried filling my second circle with every other color, and that works just fine. Also if I remove the last "ctx.beginPath();" my first circle gets painted in gradient. But I can't get the same bug to work on my second circle by changing the position of the code or something. And every guide I've found tells me that this should work, as far as I understand it.
Gradients are defined with an absolute position so if you draw your circle outside the area defined by the gradient it will appear transparent instead of filled.
There is no need to close the path as the fill() method will close it implicit for you, but just make sure the coordinates in the gradient covers the area you want to fill.
Instead of calculating for each time you need to fill an arc you could create a generic wrapper function which takes a position and colors to fill (adjust as needed):
A demo here
/**
* Fills a circle with a two-color gradient.
* #param {Number} cx - center X
* #param {Number} cy - center Y
* #param {Number} radius - radius
* #param {String} col1 - start color as CSS color string
* #param {String} col2 - end color as CSS color string
* #param {Boolean} [horiz=false] - Set true for horizontal gradient
*/
function fillCircle(cx, cy, radius, col1, col2, horiz) {
var x = cx - radius,
y = cy - radius,
d = radius * 2,
gradient;
if (horiz) {
gradient = ctx.createLinearGradient(x, 0, x+d, d);
}
else {
gradient = ctx.createLinearGradient(0, y, 0, y+d);
}
gradient.addColorStop(0, col1);
gradient.addColorStop(1, col2);
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2*Math.PI);
ctx.fill();
}
Then just use it this way:
fillCircle(200, 200, 70, 'yellow', 'red');
The last flag is optional here and makes a horizontal gradient if set to true.
Use ctx.closePath(); After each separate shape/line you want is done.
ctx.beginPath();
ctx.moveTo(25, 50);
ctx.bezierCurveTo(25, 50, 50, 80, 75, 60)
ctx.strokeStyle = "black";
ctx.stroke();
ctx.closePath();
The gradient needs to be set with the coordinates matching where your shape is on the canvas.
You have the gradient starting at 0,0,
var gradient = ctx.createLinearGradient(0, 0, 50, 0);
But your circle is locates at 25,50. Make your gradient coordinates the same as you circle coordinates.
http://jsfiddle.net/bC75t/1/
Here is an example!
I am trying to reset the green arc inside drawValueArc() so that each time you click the change button, the green arc is removed and redrawn. How can I remove it without removing the entire canvas? Also, as an aside, I have noticed that Math.random() * 405 * Math.PI / 180 doesn't actually always result in an arc that fits inside the gray arc, sometimes it is larger than the gray arc, why is this?
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cx = 150;
var cy = 150;
var startRadians = 135 * Math.PI / 180;
var endRadians = 405 * Math.PI / 180;
//main arc
ctx.beginPath();
ctx.arc(cx, cy, 58, startRadians, endRadians, false);
ctx.strokeStyle="rgb(220,220,220)";
ctx.lineWidth = 38;
ctx.stroke();
$('#setRandomValue').click(function(){
drawValueArc(Math.random() * 405 * Math.PI / 180);
});
function drawValueArc(val){
//ctx.clearRect(0, 0, W, H);
ctx.beginPath();
ctx.arc(cx, cy, 58, startRadians, val, false);
ctx.strokeStyle = "green";
ctx.lineWidth = 38;
ctx.stroke();
}
Drawing past boundary
The problem you are facing is in first instance the fact you are drawing before and after a 0-degree on the circle. This can be complicated to handle as you need to split in two draws: one for the part up to 0 (360) and one 0 to the remaining part.
There is a simple trick you can use to make this easier to deal with and that is to deal with all angles from 0 and use an offset when you draw.
Demo using redraw base (I moved it to jsfiddle as jsbin did not work for me):
http://jsfiddle.net/3dGLR/
Demo using off-screen canvas
http://jsfiddle.net/AbdiasSoftware/Dg9Jj/
First, some optimizations and settings for the offset:
var startRadians = 0; //just deal with angles
var endRadians = 300;
var deg2rad = Math.PI / 180; //pre-calculate this to save some cpu cycles
var offset = 122; //adjust this to modify rotation
We will now let the main function, drawArc() do all calculations for us so we can focus on the numbers - here we also offset the values:
function drawArc(color, start, end) {
ctx.beginPath();
ctx.arc(cx, cy, 58,
(startRadians + offset) * deg2rad,
(end + offset) * deg2rad, false);
ctx.strokeStyle = color;
ctx.lineWidth = 38;
ctx.stroke();
}
Clearing the previous arc
There are several techniques to clear the previous drawn arc:
You can draw the base arc to an off-screen canvas and use drawImage() to erase the old.
You can do as in the following example, just re-draw it with the base color
As with 2. but subtracting the green arc and draw the base color from the end of the green arc to the end of the base arc.
clearing the whole canvas with fillRect or clearRect.
1 and 3 are the fastest, while 4 is the slowest.
With out re-factored function (drawArc) it's as easy as this:
function drawValueArc(val) {
drawArc("rgb(220,220,220)", startRadians, endRadians);
drawArc("green", startRadians, val);
}
As everything now is 0-based concerning start we really don't need to give any other argument than 0 to the drawArc instead of startRadians. Use the new offset to offset the start position and adjust the endRadians to where you want it to stop.
As you can see in the demo, using this technique keeps everything in check without the need to draw in split.
Tip: if you notice green artifacts on the edges: this is due to anti-alias. Simply reduce the line width for the green color by 2 pixels (see demo 2, off-screen canvas).
It appears the only way to clear a region from a canvas is to use the clearRect() command - I need to clear a circle (I am masking out areas from a filled canvas, point lights in this specific case) and despite all attempts it does not seem possible.
I tried drawing a circle with an alpha value of 0 but simply nothing would appear unless the alpha was higher (which is counter to the point :P) - I assume because a contex.fill() draws it as an add rather than a replace.
Any suggestions on how I might be able to (quickly) clear circles for mask purposes?
Use .arc to create a circular stroke and then use .clip() to make that the current clipping region.
Then you can use .clearRect() to erase the whole canvas, but only the clipped area will change.
If you're making a game or something where squeezing every bit of performance matters, have a look at how I made this answer: Canvas - Fill a rectangle in all areas that are fully transparent
Specifically, the edit of the answer that leads to this: http://jsfiddle.net/a2Age/2/
The huge plusses here:
No use of paths (slow)
No use of clips (slow)
No need for save/restore (since there's no way to reset a clipping region without clearing all state(1), it means you must use save/restore also)
(1) I actually complained about this and resetClip() has been put in the offical spec because of it, but it will be a while before browsers implement it.
Code
var ctx = document.getElementById('canvas1').getContext('2d'),
ambientLight = 0.1,
intensity = 1,
radius = 100,
amb = 'rgba(0,0,0,' + (1 - ambientLight) + ')';
addLight(ctx, intensity, amb, 200, 200, 0, 200, 200, radius); // First circle
addLight(ctx, intensity, amb, 250, 270, 0, 250, 270, radius); // Second circle
addLight(ctx, intensity, amb, 50, 370, 0, 50, 370, radius, 50); // Third!
ctx.fillStyle = amb;
ctx.globalCompositeOperation = 'xor';
ctx.fillRect(0, 0, 500, 500);
function addLight(ctx, intsy, amb, xStart, yStart, rStart, xEnd, yEnd, rEnd, xOff, yOff) {
xOff = xOff || 0;
yOff = yOff || 0;
var g = ctx.createRadialGradient(xStart, yStart, rStart, xEnd, yEnd, rEnd);
g.addColorStop(1, 'rgba(0,0,0,' + (1 - intsy) + ')');
g.addColorStop(0, amb);
ctx.fillStyle = g;
ctx.fillRect(xStart - rEnd + xOff, yStart - rEnd + yOff, xEnd + rEnd, yEnd + rEnd);
}
canvas {
border: 1px solid black;
background-image: url('http://placekitten.com/500/500');
}
<canvas id="canvas1" width="500" height="500"></canvas>
Given the requirements, these answers are fine. But lets say you're like me and you have additional requirements:
You want to "clear" a part of a shape that may be partially outside the bounds of the shape you're clearing.
You want to see the background underneath the shape instead of clearing the background.
For the first requirement, the solution is to use context.globalCompositeOperation = 'destination-out' The blue is the first shape and the red is the second shape. As you can see, destination-out removes the section from the first shape.
Here's some example code:
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
Here's the potential problem with this: The second fill() will clear everything underneath it, including the background. Sometimes you'll want to only clear the first shape but you still want to see the layers that are underneath it.
The solution to that is to draw this on a temporary canvas and then drawImage to draw the temporary canvas onto your main canvas. The code will look like this:
diameter = projectile.radius * 2
console.log "<canvas width='" + diameter + "' height='" + diameter + "'></canvas>"
explosionCanvas = $("<canvas width='" + diameter + "' height='" + diameter + "'></canvas>")
explosionCanvasCtx = explosionCanvas[0].getContext("2d")
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
durationPercent = (projectile.startDuration - projectile.duration) / projectile.startDuration
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'source-over' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
ctx.drawImage(explosionCanvas[0], projectile.pos.x - projectile.radius, projectile.pos.y - projectile.radius) #center
You have a few options.
Firstly, here's a function we'll use to fill a circle.
var fillCircle = function(x, y, radius)
{
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
};
clip()
var clearCircle = function(x, y, radius)
{
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.clip();
context.clearRect(x - radius - 1, y - radius - 1,
radius * 2 + 2, radius * 2 + 2);
};
See this on jsFiddle.
globalCompositeOperation
var clearCircle = function(x, y, radius)
{
context.save();
context.globalCompositeOperation = 'destination-out';
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
context.restore();
};
See this on jsFiddle.
Both gave the desired result on screen, however the performance wasn't sufficient in my case as I was drawing and clearing a lot of circles each frame for an effect. In the end I found a different way to get a similar effect to what I wanted by just drawing thicker lines on an arc, but the above may still be useful to someone having different performance requirements.
Use canvas.getContext("2d").arc(...) to draw a circle over the area with the background colour?
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.arc(x, y, r, 0, 2*Math.PI, false);
context.fillStyle = "#FFFFFF";
context.fill();
Where x = left position, y = right position, r = radius, and ctx = your canvas:
function clearCircle( x , y , r ){
for( var i = 0 ; i < Math.round( Math.PI * r ) ; i++ ){
var angle = ( i / Math.round( Math.PI * r )) * 360;
ctx.clearRect( x , y , Math.sin( angle * ( Math.PI / 180 )) * r , Math.cos( angle * ( Math.PI / 180 )) * r );
}
}