I'm trying to bisect a circle with JavaScript and a <canvas> element. I used the formula given in the accepted answer to this question to find points on the edge of the circle, but for some reason when I give two opposite points on the circle (0 and 180, or 90 and 270, for example) I'm not getting a line that goes through the center of the circle.
My code, which you can see on JSFiddle, makes a nice Spirograph pattern, which is cool except that that's not what I'm trying to do.
How do I fix this so the lines go through the center?
(Ultimately I'm trying to draw a circle of fifths, but all I'm asking how to do now is get the lines to go through the center. Once that works I'll get on with the other steps to do the circle of fifths, which will obviously include drawing fewer lines and losing the Spirograph torus.)
Degrees in Javascript are specified in radians. Instead of checking for greater than or less than 180, and adding or subtracting 180, do the same with Math.PI radians.
http://jsfiddle.net/7w29h/1/
Drawing function and trigonometry function in Math expects angle to be specified in radian, not degree.
Demo
Diff with your current code:
function bisect(context, degrees, radius, cx, cy) {
// calculate the point on the edge of the circle
var x1 = cx + radius * Math.cos(degrees / 180 * Math.PI);
var y1 = cy + radius * Math.sin(degrees / 180 * Math.PI);
/* Trimmed */
// and calculate the point on the opposite side
var x2 = cx + radius * Math.cos(degrees2 / 180 * Math.PI);
var y2 = cy + radius * Math.sin(degrees2 / 180 * Math.PI);
/* Trimmed */
}
function draw(theCanvas) {
/* Trimmed */
// 2 * PI, which is 360 degree
context.arc(250, 250, 220, 0, Math.PI * 2, false);
/* Trimmed */
context.arc(250, 250, 110, 0, Math.PI * 2, false);
/* Trimmed */
// No need to go up to 360 degree, unless the increment does
// not divides 180
for (j = 2; j < 180; j = j + 3) {
bisect(context, j, 220, 250, 250);
}
/* Trimmed */
}
Appendix
This is the full source code from JSFiddle, keep the full copy here just in case.
HTML
<canvas id="the_canvas" width="500" height="500"></canvas>
CSS
canvas {
border:1px solid black;
}
JavaScript
function bisect(context, degrees, radius, cx, cy) {
// calculate the point on the edge of the circle
var x1 = cx + radius * Math.cos(degrees / 180 * Math.PI);
var y1 = cy + radius * Math.sin(degrees / 180 * Math.PI);
// get the point on the opposite side of the circle
// e.g. if 90, get 270, and vice versa
// (super verbose but easily readable)
if (degrees > 180) {
var degrees2 = degrees - 180;
} else {
var degrees2 = degrees + 180;
}
// and calculate the point on the opposite side
var x2 = cx + radius * Math.cos(degrees2 / 180 * Math.PI);
var y2 = cy + radius * Math.sin(degrees2 / 180 * Math.PI);
// now actually draw the line
context.beginPath();
context.moveTo(x1, y1)
context.lineTo(x2, y2)
context.stroke();
}
function draw(theCanvas) {
var context = theCanvas.getContext('2d');
// draw the big outer circle
context.beginPath();
context.strokeStyle = "#222222";
context.lineWidth = 2;
context.arc(250, 250, 220, 0, Math.PI * 2, false);
context.stroke();
context.closePath();
// smaller inner circle
context.beginPath();
context.strokeStyle = "#222222";
context.lineWidth = 1;
context.arc(250, 250, 110, 0, Math.PI * 2, false);
context.stroke();
context.closePath();
for (j=2; j < 180; j = j + 3) {
bisect(context, j, 220, 250, 250);
}
}
$(function () {
var theCanvas = document.getElementById('the_canvas');
console.log(theCanvas);
draw(theCanvas, 50, 0, 270);
});
Related
I have a need to add a bunch of dots within the confines of an ellipse. I'm trying to modify the code from the accepted solution to this question, which is set up to use a circle (same width and height) rather than an ellipse. It is also implementing via a HTML <canvas> element, and I need to get it working using p5.js.
Here is my code for the <canvas> element. It seems to be working great. i can size and position the ellipse any which way and the dots are all displayed within it no matter what.
function ellipse(context, cx, cy, rx, ry){
context.save(); // save state
context.beginPath();
context.translate(cx-rx, cy-ry);
context.scale(rx, ry);
context.arc(1, 1, 1, 0, 2 * Math.PI, false);
context.restore(); // restore to original state
context.stroke();
}
var canvas = document.getElementById("thecanvas");
var ctx = canvas.getContext('2d'),
count = 1000, // number of random points
cx = 300,
cy = 200,
w = 50,
h = 49,
radius = w;
ctx.fillStyle = '#CCCCCC';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ellipse(ctx, cx, cy, w, h);
// create random points
ctx.fillStyle = '#ffffff';
function dots() {
while (count) {
var pt_angle = Math.random() * 2 * Math.PI;
var pt_radius_sq = Math.random() * radius * radius;
var pt_x = Math.sqrt(pt_radius_sq) * Math.cos(pt_angle);
var pt_y = Math.sqrt(pt_radius_sq) * Math.sin(pt_angle);
ctx.fillRect( ((Math.sqrt(pt_radius_sq) * Math.cos(pt_angle) ) + cx), (((Math.sqrt(pt_radius_sq) * Math.sin(pt_angle))/(w/h)) + cy), 2, 2);
count--;
}
}
dots();
<canvas id="thecanvas" width="800" height="800"></canvas>
For some reason, what I believe to be a pretty straight forward to port to p5.js is not working. The dots seem to be contained within the same shaped ellipse as what is defined, but the scale seems to be off and I can't really tell what is causing it. You can see this in action at https://editor.p5js.org/dpassudetti/sketches/YRbLuwoM2 - p5.js code pasted below as well:
var count = 1000, // number of random points
cx = 300,
cy = 200,
w = 50,
h = 49,
radius = w;
function setup() {
createCanvas(800, 800);
function dots() {
fill('green');
stroke('green');
while (count) {
var pt_angle = Math.random() * 2 * Math.PI;
var pt_radius_sq = Math.random() * radius * radius;
var pt_x = Math.sqrt(pt_radius_sq) * Math.cos(pt_angle);
var pt_y = Math.sqrt(pt_radius_sq) * Math.sin(pt_angle);
square(
Math.sqrt(pt_radius_sq) * Math.cos(pt_angle) + cx,
(Math.sqrt(pt_radius_sq) * Math.sin(pt_angle)) / (w / h) + cy,
2,
2
);
count--;
}
}
fill("#CCCCCC");
ellipse(cx, cy, w, h);
dots();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
If anyone can see what's throwing the scale off for the p5.js version and wouldn't mind pointing me in the right direction, I'd be most appreciative.
The function you are using to draw the ellipse takes different parameters in the 1st and 2nd case. In one case it's taking the radiuses, and in the other the diameters, so you're off by a factor of 0.5.
I've simplified your code to consider just circles and kept just the w variable, but you can use the code below and add back the h variable if you wish to.
var count = 300, // number of random points
cx = 300,
cy = 200,
w = 50,
radius = w / 2;
function setup() {
createCanvas(800, 800);
function dots() {
fill('green');
stroke('green');
while (count) {
const pt_angle = Math.random() * 2 * Math.PI;
const px = Math.random() * radius * cos(pt_angle);
const py = Math.random() * radius * sin(pt_angle);
square(
px + cx,
py + cy,
2,
2
);
count--;
}
}
fill("#CCCCCC");
ellipse(cx, cy, w, w);
dots();
}
I want to make a gradient that covers the whole canvas whatever the angle of it.
So I used a method found on a Stack Overflow post which is finally incorrect. The solution is almost right but, in fact, the canvas is not totally covered by the gradient.
It is this answer: https://stackoverflow.com/a/45628098/5594331
(You have to look at the last point named "Example of best fit.")
In my code example below, the yellow part should not be visible because it should be covered by the black and white gradient. This is mostly the code written in Blindman67's answer with some adjustments to highlight the problem.
I have drawn in green the control points of the gradient. With the right calculations, these should be stretched to the edges of the canvas at any angle.
var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
function bestFitGradient(angle){
var dist = Math.sqrt(w * w + h * h) / 2; // get the diagonal length
var diagAngle = Math.asin((h / 2) / dist); // get the diagonal angle
// Do the symmetry on the angle (move to first quad
var a1 = ((angle % (Math.PI *2))+ Math.PI*4) % (Math.PI * 2);
if(a1 > Math.PI){ a1 -= Math.PI }
if(a1 > Math.PI / 2 && a1 <= Math.PI){ a1 = (Math.PI / 2) - (a1 - (Math.PI / 2)) }
// get angles from center to edges for along and right of gradient
var ang1 = Math.PI/2 - diagAngle - Math.abs(a1);
var ang2 = Math.abs(diagAngle - Math.abs(a1));
// get distance from center to horizontal and vertical edges
var dist1 = Math.cos(ang1) * h;
var dist2 = Math.cos(ang2) * w;
// get the max distance
var scale = Math.max(dist2, dist1) / 2;
// get the vector to the start and end of gradient
var dx = Math.cos(angle) * scale;
var dy = Math.sin(angle) * scale;
var x0 = w / 2 + dx;
var y0 = h / 2 + dy;
var x1 = w / 2 - dx;
var y1 = h / 2 - dy;
// create the gradient
const g = ctx.createLinearGradient(x0, y0, x1, y1);
// add colours
g.addColorStop(0, "yellow");
g.addColorStop(0, "white");
g.addColorStop(.5, "black");
g.addColorStop(1, "white");
g.addColorStop(1, "yellow");
return {
g: g,
x0: x0,
y0: y0,
x1: x1,
y1: y1
};
}
function update(timer){
var r = bestFitGradient(timer / 1000);
// draw gradient
ctx.fillStyle = r.g;
ctx.fillRect(0,0,w,h);
// draw points
ctx.lineWidth = 3;
ctx.fillStyle = '#00FF00';
ctx.strokeStyle = '#FF0000';
ctx.beginPath();
ctx.arc(r.x0, r.y0, 5, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.arc(r.x1, r.y1, 5, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.fill();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
border : 2px solid red;
}
<canvas id="canvas" width="300" height="200"></canvas>
In this fiddle there is a function that calculates the distance between a rotated line and a point:
function distanceToPoint(px, py, angle) {
const cx = width / 2;
const cy = height / 2;
return Math.abs((Math.cos(angle) * (px - cx)) - (Math.sin(angle) * (py - cy)));
}
Which is then used to find the maximum distance between the line and the corner points (only two points are considered, because the distances to the other two points are mirrored):
const dist = Math.max(
distanceToPoint(0, 0, angle),
distanceToPoint(0, height, angle)
);
Which can be used to calculate offset points for the end of the gradient:
const ox = Math.cos(angle) * dist;
const oy = Math.sin(angle) * dist;
const gradient = context.createLinearGradient(
width / 2 + ox,
height / 2 + oy,
width / 2 - ox,
height / 2 - oy
)
I have drawn the curved line using below lines of code, I need to draw an arrowhead.For this I need to draw 2 lines wth some angle and rotate it some some angle. It is very confusing to draw. I am following the post present in the link provided for arrowhead.
.html
<canvas id = "canvas" width = "100px" height = "120px"></canvas>
.ts
arrow({ x: 10, y: 10 }, { x: 100, y: 140 }, 15); //function called on reload.
function arrow(p1, p2, size) {
var angle = Math.atan2((p2.y - p1.y), (p2.x - p1.x));
//curve line
ctx.strokeStyle = 'white';
ctx.beginPath();
ctx.lineWidth=3;
ctx.moveTo(40,0);
ctx.bezierCurveTo(30, 0, -70, 75, 100, 150);
ctx.lineTo(100,120)
ctx.stroke();
//to draw a triangle ??
}
I tried look a like
arrow({ x: 10, y: 10 }, { x: 100, y: 140 }, 15); //function called on reload.
function arrow(p1, p2, size) {
var angle = Math.atan2((p2.y - p1.y), (p2.x - p1.x));
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//curve line
ctx.fillStyle = "";
ctx.fillRect(0,0,200,200);
ctx.strokeStyle = 'white';
ctx.beginPath();
ctx.lineWidth=3;
ctx.moveTo(40,20);
ctx.bezierCurveTo(30,40, -0,110, 100, 149.5);
ctx.moveTo(100,150.6);
ctx.lineTo(82,133);
ctx.stroke();
ctx.moveTo(100,149.7);
ctx.lineTo(76,146);
ctx.stroke();
//to draw a triangle ??
}
<canvas id = "canvas" width = "150px" height = "300px"></canvas>
You need some mathematics for this. This javascript method will draw a simple arrow head at the end of a line using canvas. It doesn't matter from which angle you come into the "arrow head".
//..come to the end of the line at some point
ctx.lineTo(tox, toy);
//draw the arrow head
ctx.lineTo(tox - headlen * Math.cos(angle + Math.PI / 5), toy - headlen * Math.sin(angle + Math.PI / 5));
ctx.moveTo(tox, toy);
ctx.lineTo(tox - headlen * Math.cos(angle - Math.PI / 5), toy - headlen * Math.sin(angle - Math.PI / 5));
I have two lines that make a 90 degree angle, I hope. I want to make it so that the vertical line moves down to the horizontal line. The angle is the pivot point. so the angle should decrease to 0 I guess. 45 would be half way.
the length of the lines should be the same during the animation.
the animation should be looping. It should go from 90 degree angle to
0 degree angle and back.
1 way I was thinking to figure this out was to change the context.moveTo(50,50) the parameters numbers so the line should begin to be drawn at the new coordinates during the animation. I had problems keeping the line the same size as the horizontal.
another way I was thinking was to change the Math.atan2. I don't know have it start at 90 degrees then go to 0 and have that reflect on the moveto parameters I don't know how to put this together.
I would prefer to use a solution with trigonometry because that is what I'm trying to get good at
for extra help if you could attach a hypotenuse so I could see the angle change the size of the triangle that would be great. That was my original problem. Thanks
window.onload = function(){
var canvas =document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 50
context.beginPath();
context.moveTo(50,50)
context.lineTo(50,200);
context.stroke();
context.closePath();
context.beginPath();
context.moveTo(50, 200);
context.lineTo(200, 200)
context.stroke();
context.closePath();
var p1 = {
x: 50,
y : 50
}
var p2 = {
x: 50,
y: 200
}
var angleDeg = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
console.log(angleDeg)
}
<canvas id="canvas" width="400" height="400"></canvas>
This might help.
window.onload = function() {
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 150;
var angle = 270;
var maxAngle = 360;
var minAngle = 270;
var direction = 0;
var p1 = {
x: 50,
y: 200
};
var p2 = {
x: 200,
y: 200
};
context.fillStyle = "rgba( 255, 0, 0, 0.5)";
function draw() {
context.clearRect(0, 0, 400, 400);
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y)
if (angle >= maxAngle) {
direction = 1;
} else if (angle <= minAngle) {
direction = 0;
}
if (direction == 0) {
angle++;
} else {
angle--;
}
var x = p1.x + length * Math.cos(angle * Math.PI / 180);
var y = p1.y + length * Math.sin(angle * Math.PI / 180);
context.moveTo(p1.x, p1.y);
context.lineTo(x, y);
context.lineTo(p2.x, p2.y);
context.stroke();
context.fill()
context.closePath();
}
setInterval(draw, 50);
}
<canvas id="canvas" width="400" height="400"></canvas>
To get angle sequence (in degrees) like 90-45-0-45-90-45..., you can use this simple algo (pseudocode):
i = 0
while (drawingNeeded) do
angle = Math.Abs(90 - (i % 180)) * Math.PI / 180;
endPoint.x = centerPoint.x + lineLength * Math.Cos(angle);
endPoint.y = centerPoint.y + lineLength * Math.Sin(angle);
//redraw canvas, draw static objects
drawLine(centerPoint, endPoint);
i++;
I have some 9 circles in my html page.Each circle has to be filled with certain color with certain percentage.I have drawn the circles using html5 canvas element.But i was only able to fill the enitre circle with a color and not certain percantage area.How can i achieve that?
"Fuel tank", filling of circle
Use composite mode:
Use the radius x2 for height (or width)
Draw and fill the complete circle
Use composite mode destination-out
Draw a filled rectangle on top representing the % of the height
The main code would be:
var o = radius * 2, // diameter => width and height of rect
h = o - (o * percent / 100); // height based on percentage
ctx.beginPath();
ctx.arc(x, y, radius, 0, 6.28);
ctx.fill();
ctx.globalCompositeOperation = "destination-out";
ctx.fillRect(x - radius, y - radius, o, h); // this will remove a part of the top
Demo
var ctx = document.querySelector("canvas").getContext("2d"),
pst = 0, dlt = 2;
ctx.fillStyle = "#28f";
function drawCircle(ctx, x, y, radius, percent) {
var o = radius * 2,
h = o - (o * percent / 100);
ctx.globalCompositeOperation = "source-over"; // make sure we have default mode
ctx.beginPath(); // fill an arc
ctx.arc(x, y, radius, 0, 6.28);
ctx.fill();
ctx.globalCompositeOperation = "destination-out"; // mode to use for next op.
ctx.fillRect(x - radius, y - radius, o, h); // "clear" part of arc
ctx.globalCompositeOperation = "source-over"; // be polite, set default mode back
}
(function loop() {
ctx.clearRect(0,0,300,150);
drawCircle(ctx, 70, 70, 60, pst);
pst += dlt;
if (pst <= 0 || pst >= 100) dlt = -dlt;
requestAnimationFrame(loop);
})();
<canvas></canvas>
Pie type
Move to center of circle
Add arc, this will create a line from center to start of arc
Close path, this will create a line from end of arc back to center, and fill
(tip: closePath() is really not necessary with fill() as fill() will close the path implicit, but it's needed if you want to do a stroke() instead).
The essential part being:
ctx.beginPath();
ctx.moveTo(x, y);
ctx.arc(x, y, radius, 0, 2 * Math.PI * percent / 100);
//ctx.closePath(); // for stroke, not needed for fill
ctx.fill();
Demo
var ctx = document.querySelector("canvas").getContext("2d"),
pst = 0, dlt = 2;
ctx.fillStyle = "#28f";
function drawPie(ctx, x, y, radius, percent) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.arc(x, y, radius, 0, 2 * Math.PI * percent /100);
ctx.fill();
}
(function loop() {
ctx.clearRect(0,0,300,150); drawPie(ctx, 70, 70, 60, pst);
pst += dlt; if (pst <= 0 || pst >= 100) dlt = -dlt;
requestAnimationFrame(loop);
})();
<canvas></canvas>
Outlined circle:
Almost same as with pie type, but with these changes:
Move to outer edge of arc at angle 0 (or the angle you want to start from)
Add arc to path
Stroke (remember to set lineWidth, see demo below)
Essential part:
ctx.beginPath();
ctx.moveTo(x + radius, y); // cos(0) for x = 1, so just use radius, sin(0) = 0
ctx.arc(x, y, radius, 0, 2 * Math.PI * percent /100);
ctx.stroke();
You can adjust gap point using rotation transform or calculating the actual point using trigonometry.
Demo
var ctx = document.querySelector("canvas").getContext("2d"),
pst = 0, dlt = 2;
ctx.strokeStyle = "#28f";
ctx.lineWidth = 8;
function drawWedge(ctx, x, y, radius, percent) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arc(x, y, radius, 0, 2 * Math.PI * percent /100);
ctx.stroke();
}
(function loop() {
ctx.clearRect(0,0,300,150); drawWedge(ctx, 70, 70, 60, pst);
pst += dlt; if (pst <= 0 || pst >= 100) dlt = -dlt;
requestAnimationFrame(loop);
})();
<canvas></canvas>
Using different starting point
You can change the starting point for the arc using rotation transform or calculating the point manually using trigonometry.
To calculate these manually you can do (angles in radians):
x = radius * Math.cos(angleInRad); // end point for x
y = radius * Math.sin(angleInRad); // end point for y
Just add the total angle to the start angle to get end point.
360° in radians = 2 x PI, so if you want to use angles in degrees, convert them using:
angleInRad = angleInDeg * Math.PI / 180;
Demo, rotated using transfrom and counter-clock-wise mode
var ctx = document.querySelector("canvas").getContext("2d"),
pst = 0, dlt = 2;
ctx.strokeStyle = "#28f";
ctx.lineWidth = 8;
function drawWedge(ctx, x, y, radius, percent) {
ctx.translate(x, y); // translate to rotating pivot
ctx.rotate(Math.PI * 0.5); // rotate, here 90° deg
ctx.translate(-x, -y); // translate back
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arc(x, y, radius, 0, 2 * Math.PI * percent /100);
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0); // reset transform
}
(function loop() {
ctx.clearRect(0,0,300,150); drawWedge(ctx, 70, 70, 60, pst);
pst += dlt; if (pst <= 0 || pst >= 100) dlt = -dlt;
requestAnimationFrame(loop);
})();
<canvas></canvas>