Canvas determine if point is on line - javascript

I am trying to design a line chart which will show a tool tip when the user hovers over a line. Below is a minimal example showing my attempt at determining whether a point lies on a line (the line can be considered a rectangle with height 5 and width 80 in this case).
I don't understand why isPointInPath can only find the points at exactly the start and end points of the line. How can I determine if a point lies anywhere on the line?
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.lineWidth = 5;
ctx.moveTo(20, 20);
ctx.lineTo(100, 20);
ctx.stroke();
console.log(ctx.isPointInPath(20, 20)); // beginning: true
console.log(ctx.isPointInPath(100, 20)); // end: true
console.log(ctx.isPointInPath(60, 20)); // middle: false
console.log(ctx.isPointInPath(100, 21)); // end offset: false

isPointInPath does use the fill region to make its check. You want isPointInStroke() which also takes the lineWidth into consideration:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const path = new Path2D();
let x = -1;
let y = -1;
for (let i = 0; i<20; i++) {
path.lineTo(
Math.random() * canvas.width,
Math.random() * canvas.height
);
}
ctx.lineWidth = 5;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = ctx.isPointInStroke(path, x, y)
? "red"
: "green";
ctx.stroke(path);
}
draw();
addEventListener("mousemove", (evt) => {
const rect = canvas.getBoundingClientRect();
x = evt.clientX - rect.left;
y = evt.clientY - rect.top;
draw();
});
<canvas></canvas>
As to why isPointInPath() finds the points that are explicitly set in the Path declaration and not the ones that would logically lie on it... That's quite unclear I'm afraid, the specs don't have a clear fill algorithm, and I'll open a specs issue to fix that since there is actually an interop-issue with Safari acting as you'd expect, and Firefox + Chrome ignoring the fill area entirely when it doesn't produce a pixel, but still keeping these points.

If you want a function that tells you if a point is on a line with a width of 5px, we should use some analytical geometry: a point distance from a line. I copied formula from wikipedia
function isPointOnLine(px, py, x1, y1, x2, y2, width) {
return distancePointFromLine(px, py, x1, y1, x2, y2, width) <= width / 2
}
function distancePointFromLine(x0, y0, x1, y1, x2, y2) {
return Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) / Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
}
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.lineWidth = 5;
x1 = y1 = y2 = 20
x2 = 100
ctx.moveTo(20, 20);
ctx.lineTo(100, 20);
ctx.stroke();
console.log(isPointOnLine(20, 20, x1, y1, x2, y2, 5));
console.log(isPointOnLine(100, 20, x1, y1, x2, y2, 5));
console.log(isPointOnLine(60, 20, x1, y1, x2, y2, 5));
console.log(isPointOnLine(100, 21, x1, y1, x2, y2, 5));
<canvas></canvas>

Related

Javascript sin function does some funky stuff

When I wrote this script to calculate the angle from 2 points (I know I can use atan2, but experiments and math learning), I got some weird results when I combined it with a triangle function I wrote. It suddenly turns around when the halfway point on the x axis is passed with the mouse, and as I don't know much about trigonometery I didn't know how to fix this issue... Here's my code:
const can = document.getElementById('can');
const ctx = can.getContext('2d');
can.width = innerWidth;
can.height = innerHeight;
var length = 200;
var origin = {x: can.width / 2, y: can.height / 2};
var mousey;
var mousex;
function triangle(x1, y1, x2, y2){
ctx.strokeStyle = "blue";
ctx.lineWidth = "2";
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2, y2 + (y1 - y2));
ctx.strokeStyle = "#33cc33";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2 + (y1 - y2));
ctx.lineTo(x1, y1);
ctx.strokeStyle = "red";
ctx.stroke();
}
(function loop(){
ctx.clearRect(0, 0, can.width, can.height);
calc2(mousex, mousey);
requestAnimationFrame(loop);
})();
function update(){
calc2(mousex, mousey);
}
function calc2(x1, y1){
let x2 = origin.x;
let y2 = origin.y;
let opposite = y1 - y2;
let hypotenuse = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
let a = Math.asin(opposite / hypotenuse);
triangle(x2, y2, x2 + Math.cos(a) * length, y2 + Math.sin(a) * length);
}
addEventListener('mousemove', (e)=>{
mousey = e.clientY;
mousex = e.clientX;
});
<canvas id="can" width="400" height="400"></canvas>
Thanks in advance!
In function calc2(x1, y1), variable 'hypotenuse' is always a non-negative number, it can't tell if your mouse is on the left or right side. You need a boolean to store this.
The range of Math.asin() is from -pi/2 to pi/2 (-90 degrees to 90 degrees). Cos() this angle is always greater or equal to 0. In your calculations, the -90 deg relative to the horizontal red line is a straight downward line. Rotating counterclockwise from there, -45 deg is a line to lower right corner, 0 deg is the red line itself, 45 deg to upper right corner, 90 deg is straight upward. That means all possible blue lines are from starting point to the right.
The y-axis is correct. To let cos(x) produce a negative number, x should be pi/2 to pi (90 deg to 180 deg). As cos(pi-x) == -cos(x), we will flip this angle when mouse is on the left side.
const can = document.getElementById('can');
const ctx = can.getContext('2d');
can.width = innerWidth;
can.height = innerHeight;
var length = 200;
var origin = {x: can.width / 2, y: can.height / 2};
var mousey;
var mousex;
function triangle(x1, y1, x2, y2){
ctx.strokeStyle = "blue";
ctx.lineWidth = "2";
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2, y2 + (y1 - y2));
ctx.strokeStyle = "#33cc33";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2 + (y1 - y2));
ctx.lineTo(x1, y1);
ctx.strokeStyle = "red";
ctx.stroke();
}
(function loop(){
ctx.clearRect(0, 0, can.width, can.height);
calc2(mousex, mousey);
requestAnimationFrame(loop);
})();
function update(){
calc2(mousex, mousey);
}
function calc2(x1, y1){
let x2 = origin.x;
let y2 = origin.y;
let opposite = y1 - y2;
let hypotenuse = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
let onLeft = x1 < x2 ? true : false;
let a = Math.asin(opposite / hypotenuse);
triangle(x2, y2, x2 + Math.cos(onLeft ? Math.PI - a : a) * length, y2 + Math.sin(a) * length)
}
addEventListener('mousemove', (e)=>{
mousey = e.clientY;
mousex = e.clientX;
});
<canvas id="can" width="400" height="400"></canvas>

Moving an object on a path using p5.js

I'm trying to write a code that mimics what happens in this video in an HTML canvas.
My code differs in that each time the page loads or is refreshed, the radiuses of the two circles are randomly generated. I need the "planets" to travel at the same speed along the circumferences of their respective circles.
I'm using the p5.js to draw to the canvas. Is there something in p5.js that draws an object according to a path, in my case a path that is a circle?
I've looked through the references and came across vectors but I don't quite understand what they do.
My code so far:
var w = window.innerWidth;
var h = window.innerHeight;
var r1, r1;
var d;
var x1, x2, y1, y2;
var angle = Math.PI / 4;
function setup() {
// canvas setup
createCanvas(w, h);
background(255);
angleMode(RADIANS);
// randomly generated radiuses
r1 = Math.floor(Math.random() * (h/2-300)) + 300;
r2 = Math.floor(Math.random() * (200-100)) + 100;
// drawing the two ellipses
ellipseMode(CENTER);
noFill();
ellipse(w/2, h/2, r1*2, r1*2);
ellipse(w/2, h/2, r2*2, r2*2);
}
function draw() {
// points on the circles
x1 = (r1 * (Math.cos(angle))) + (w/2);
y1 = (h/2) - (r1 * (Math.sin(angle)));
x2 = (r2 * (Math.cos(angle))) + (w/2);
y2 = (h/2) - (r2 * (Math.sin(angle)));
// drawing two circles at those points
ellipseMode(CENTER);
noStroke();
fill('rgb(140, 140, 140)');
ellipse(x1, y1, 20, 20);
ellipse(x2, y2, 20, 20);
// randomly generated color for the line
var r = random(0, 255);
var g = random(0, 255);
var b = random(0, 255);
stroke(r, g, b);
strokeWeight(1);
// drawing the line that connects the two points
line(x1, y1, x2, y2);
// animation????
angle = angle + (10 * (Math.PI / 180));
}
The problem with the last line is that it creates evenly spaces lines, not the pattern that is created in the video.
If the two planets move with the same angle increment they will always retain a relationship causing an evenly spaced line between them.
In order for a line connected between them to cross the center they have to have different incremental values. You would have to maintain two different angle values as well as step (or speed) per angle.
For the example in the video the speed ratio is 1:2.247 based on a real-world relationship between earth and Venus' day-ratio around the sun. Since they differ in speed the line between them will now cross over and produce the pentagram-ish pattern.
I don't know P5.js so I have added a plain JavaScript example below which can be considered pseudo code in case P5 is a requirement. From that you will be able to see how to calculate the two positions at variable speeds.
Example
var ctx = c.getContext("2d"),
ratio = 2.247, // earth:venus ratio 1:2.247
angle1 = 0, // planet 1 current angle
angle2 = 0, // planet 2 current angle
dlt = 0.05, // angle step (speed)
radius1 = 150, // radius path for planet 1
radius2 = 100, // radius path for planet 2
cx = c.width*0.5, // center canvas
cy = c.height*0.5,
t = 503; // abort animation per # of frames
ctx.strokeStyle = "rgba(0,120,255,0.5)";
(function loop() {
var x1, y1, x2, y2;
x1 = cx + Math.cos(angle1) * radius1; // current (x,y) for planet 1
y1 = cy + Math.sin(angle1) * radius1;
x2 = cx + Math.cos(angle2) * radius2; // current (x,y) for planet 2
y2 = cy + Math.sin(angle2) * radius2;
ctx.beginPath(); // draw line
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
angle1 += dlt; // increase angle planet 1
angle2 += dlt * ratio; // increase angle planet 2 per ratio
if (t--) requestAnimationFrame(loop)
})()
<canvas id=c height=300></canvas>

How can I draw arrows on a canvas with mouse

recently I started playing with canvas element. Now I am able to draw lines(as many as I wish) on a canvas with mouse. You can see it here in the code: https://jsfiddle.net/saipavan579/a6L3ka8p/.
var ctx = tempcanvas.getContext('2d'),
mainctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
x1,
y1,
isDown = false;
tempcanvas.onmousedown = function(e) {
var pos = getPosition(e, canvas);
x1 = pos.x;
y1 = pos.y;
isDown = true;
}
tempcanvas.onmouseup = function() {
isDown = false;
mainctx.drawImage(tempcanvas, 0, 0);
ctx.clearRect(0, 0, w, h);
}
tempcanvas.onmousemove = function(e) {
if (!isDown) return;
var pos = getPosition(e, canvas);
x2 = pos.x;
y2 = pos.y;
ctx.clearRect(0, 0, w, h);
drawEllipse(x1, y1, x2, y2);
}
function drawEllipse(x1, y1, x2, y2) {
var radiusX = (x2 - x1) * 0.5,
radiusY = (y2 - y1) * 0.5,
centerX = x1 + radiusX,
centerY = y1 + radiusY,
step = 0.01,
a = step,
pi2 = Math.PI * 2 - step;
ctx.beginPath();
ctx.moveTo(x1,y1);
for(; a < pi2; a += step) {
ctx.lineTo(x2,y2);
}
ctx.closePath();
ctx.strokeStyle = '#000';
ctx.stroke();
}
function getPosition(e, gCanvasElement) {
var x;
var y;
x = e.pageX;
y = e.pageY;
x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;
return {x:x, y:y};
};
Now I want to draw arrow headed lines(for pointing to some specific point on a image) in the same way as I am drawing the lines. How can do that? Thank you in advance.
You can draw an arrowhead at the end of line segment [p0,p1] like this:
calculate the angle from p0 to p1 using Math.atan2.
Each side of the arrowhead starts at p1, so calculate the 2 arrow endpoints using trigonometry.
draw the [p0,p1] line segment and the 2 arrowhead line segments.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var p0={x:50,y:100};
var p1={x:250,y:50};
drawLineWithArrowhead(p0,p1,15);
function drawLineWithArrowhead(p0,p1,headLength){
// constants (could be declared as globals outside this function)
var PI=Math.PI;
var degreesInRadians225=225*PI/180;
var degreesInRadians135=135*PI/180;
// calc the angle of the line
var dx=p1.x-p0.x;
var dy=p1.y-p0.y;
var angle=Math.atan2(dy,dx);
// calc arrowhead points
var x225=p1.x+headLength*Math.cos(angle+degreesInRadians225);
var y225=p1.y+headLength*Math.sin(angle+degreesInRadians225);
var x135=p1.x+headLength*Math.cos(angle+degreesInRadians135);
var y135=p1.y+headLength*Math.sin(angle+degreesInRadians135);
// draw line plus arrowhead
ctx.beginPath();
// draw the line from p0 to p1
ctx.moveTo(p0.x,p0.y);
ctx.lineTo(p1.x,p1.y);
// draw partial arrowhead at 225 degrees
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(x225,y225);
// draw partial arrowhead at 135 degrees
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(x135,y135);
// stroke the line and arrowhead
ctx.stroke();
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>

Simple JS/html5 fractal tree with decrementing line width

I would like to make a very simple fractal tree (for learning purposes) using JS. I have been using the following code which i got from a wikipedia article. It is great, only i want the line width to decrement with each iteration. As you can see i tried context.lineWidth = context.lineWidth - 1, but this doesn’t work. Does anybody have any ideas about how this can be acheived?
var elem = document.getElementById('canvas');
var context = elem.getContext('2d');
context.fillStyle = '#000';
context.lineWidth = 20;
var deg_to_rad = Math.PI / 180.0;
var depth = 9;
function drawLine(x1, y1, x2, y2){
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.lineWidth = context.lineWidth - 1;
}
function drawTree(x1, y1, angle, depth){
if (depth != 0){
var x2 = x1 + (Math.cos(angle * deg_to_rad) * depth * 10.0);
var y2 = y1 + (Math.sin(angle * deg_to_rad) * depth * 10.0);
drawLine(x1, y1, x2, y2);
drawTree(x2, y2, angle - 20, depth - 1);
drawTree(x2, y2, angle + 20, depth - 1);
}
}
context.beginPath();
drawTree(300, 500, -90, depth);
context.closePath();
context.stroke();
It would also be great if there was a way to do this in stages so that when i click on a button it adds a new branch. Anyway, your advice would be greatly appreciated.
I have created and tweaked a fiddle which somehow does what you want.
fiddle
All in all: You need to stroke each time a new line width is set. So the code looks like this:
function drawLine(x1, y1, x2, y2, lw){
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.lineWidth = lw;
context.closePath();
context.stroke();
}
function drawTree(x1, y1, angle, depth, lw){
if (depth != 0){
var x2 = x1 + (Math.cos(angle * deg_to_rad) * depth * 10.0);
var y2 = y1 + (Math.sin(angle * deg_to_rad) * depth * 10.0);
drawLine(x1, y1, x2, y2, lw);
drawTree(x2, y2, angle - 20, depth - 1, lw - 1);
drawTree(x2, y2, angle + 20, depth - 1, lw - 1);
}
}
drawTree(300, 500, -90, depth, depth);

How to draw an oval in html5 canvas?

There doesnt seem to be a native function to draw an oval-like shape. Also i am not looking for the egg-shape.
Is it possible to draw an oval with 2 bezier curves?
Somebody expierenced with that?
My purpose is to draw some eyes and actually im just using arcs.
Thanks in advance.
Solution
So scale() changes the scaling for all next shapes.
Save() saves the settings before and restore is used to restore the settings to draw new shapes without scaling.
Thanks to Jani
ctx.save();
ctx.scale(0.75, 1);
ctx.beginPath();
ctx.arc(20, 21, 10, 0, Math.PI*2, false);
ctx.stroke();
ctx.closePath();
ctx.restore();
updates:
scaling method can affect stroke width appearance
scaling method done right can keep stroke width intact
canvas has ellipse method that Chrome now supports
added updated tests to JSBin
JSBin Testing Example (updated to test other's answers for comparison)
Bezier - draw based on top left containing rect and width/height
Bezier with Center - draw based on center and width/height
Arcs and Scaling - draw based on drawing circle and scaling
see Deven Kalra's answer
Quadratic Curves - draw with quadratics
test appears to not draw quite the same, may be implementation
see oyophant's answer
Canvas Ellipse - using W3C standard ellipse() method
test appears to not draw quite the same, may be implementation
see Loktar's answer
Original:
If you want a symmetrical oval you could always create a circle of radius width, and then scale it to the height you want (edit: notice this will affect stroke width appearance - see acdameli's answer), but if you want full control of the ellipse here's one way using bezier curves.
<canvas id="thecanvas" width="400" height="400"></canvas>
<script>
var canvas = document.getElementById('thecanvas');
if(canvas.getContext)
{
var ctx = canvas.getContext('2d');
drawEllipse(ctx, 10, 10, 100, 60);
drawEllipseByCenter(ctx, 60,40,20,10);
}
function drawEllipseByCenter(ctx, cx, cy, w, h) {
drawEllipse(ctx, cx - w/2.0, cy - h/2.0, w, h);
}
function drawEllipse(ctx, x, y, w, h) {
var kappa = .5522848,
ox = (w / 2) * kappa, // control point offset horizontal
oy = (h / 2) * kappa, // control point offset vertical
xe = x + w, // x-end
ye = y + h, // y-end
xm = x + w / 2, // x-middle
ym = y + h / 2; // y-middle
ctx.beginPath();
ctx.moveTo(x, ym);
ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
//ctx.closePath(); // not used correctly, see comments (use to close off open path)
ctx.stroke();
}
</script>
Here is a simplified version of solutions elsewhere. I draw a canonical circle, translate and scale and then stroke.
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();
}
There is now a native ellipse function for canvas, very similar to the arc function although now we have two radius values and a rotation which is awesome.
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
Live Demo
ctx.ellipse(100, 100, 10, 15, 0, 0, Math.PI*2);
ctx.fill();
Only seems to work in Chrome currently
The bezier curve approach is great for simple ovals. For more control, you can use a loop to draw an ellipse with different values for the x and y radius (radiuses, radii?).
Adding a rotationAngle parameter allows the oval to be rotated around its center by any angle. Partial ovals can be drawn by changing the range (var i) over which the loop runs.
Rendering the oval this way allows you to determine the exact x,y location of all points on the line. This is useful if the postion of other objects depend on the location and orientation of the oval.
Here is an example of the code:
for (var i = 0 * Math.PI; i < 2 * Math.PI; i += 0.01 ) {
xPos = centerX - (radiusX * Math.sin(i)) * Math.sin(rotationAngle * Math.PI) + (radiusY * Math.cos(i)) * Math.cos(rotationAngle * Math.PI);
yPos = centerY + (radiusY * Math.cos(i)) * Math.sin(rotationAngle * Math.PI) + (radiusX * Math.sin(i)) * Math.cos(rotationAngle * Math.PI);
if (i == 0) {
cxt.moveTo(xPos, yPos);
} else {
cxt.lineTo(xPos, yPos);
}
}
See an interactive example here: http://www.scienceprimer.com/draw-oval-html5-canvas
You could also try using non-uniform scaling. You can provide X and Y scaling, so simply set X or Y scaling larger than the other, and draw a circle, and you have an ellipse.
You need 4 bezier curves (and a magic number) to reliably reproduce an ellipse. See here:
www.tinaja.com/glib/ellipse4.pdf
Two beziers don't accurately reproduce an ellipse. To prove this, try some of the 2 bezier solutions above with equal height and width - they should ideally approximate a circle but they won't. They'll still look oval which goes to prove they aren't doing what they are supposed to.
Here's something that should work:
http://jsfiddle.net/BsPsj/
Here's the code:
function ellipse(cx, cy, w, h){
var ctx = canvas.getContext('2d');
ctx.beginPath();
var lx = cx - w/2,
rx = cx + w/2,
ty = cy - h/2,
by = cy + h/2;
var magic = 0.551784;
var xmagic = magic*w/2;
var ymagic = h*magic/2;
ctx.moveTo(cx,ty);
ctx.bezierCurveTo(cx+xmagic,ty,rx,cy-ymagic,rx,cy);
ctx.bezierCurveTo(rx,cy+ymagic,cx+xmagic,by,cx,by);
ctx.bezierCurveTo(cx-xmagic,by,lx,cy+ymagic,lx,cy);
ctx.bezierCurveTo(lx,cy-ymagic,cx-xmagic,ty,cx,ty);
ctx.stroke();
}
I did a little adaptation of this code (partially presented by Andrew Staroscik) for peoplo who do not want a so general ellipse and who have only the greater semi-axis and the excentricity data of the ellipse (good for astronomical javascript toys to plot orbits, for instance).
Here you go, remembering that one can adapt the steps in i to have a greater precision in the drawing:
/* draw ellipse
* x0,y0 = center of the ellipse
* a = greater semi-axis
* exc = ellipse excentricity (exc = 0 for circle, 0 < exc < 1 for ellipse, exc > 1 for hyperbole)
*/
function drawEllipse(ctx, x0, y0, a, exc, lineWidth, color)
{
x0 += a * exc;
var r = a * (1 - exc*exc)/(1 + exc),
x = x0 + r,
y = y0;
ctx.beginPath();
ctx.moveTo(x, y);
var i = 0.01 * Math.PI;
var twoPi = 2 * Math.PI;
while (i < twoPi) {
r = a * (1 - exc*exc)/(1 + exc * Math.cos(i));
x = x0 + r * Math.cos(i);
y = y0 + r * Math.sin(i);
ctx.lineTo(x, y);
i += 0.01;
}
ctx.lineWidth = lineWidth;
ctx.strokeStyle = color;
ctx.closePath();
ctx.stroke();
}
My solution is a bit different than all of these. Closest I think is the most voted answer above though, but I think this way is a bit cleaner and easier to comprehend.
http://jsfiddle.net/jaredwilli/CZeEG/4/
function bezierCurve(centerX, centerY, width, height) {
con.beginPath();
con.moveTo(centerX, centerY - height / 2);
con.bezierCurveTo(
centerX + width / 2, centerY - height / 2,
centerX + width / 2, centerY + height / 2,
centerX, centerY + height / 2
);
con.bezierCurveTo(
centerX - width / 2, centerY + height / 2,
centerX - width / 2, centerY - height / 2,
centerX, centerY - height / 2
);
con.fillStyle = 'white';
con.fill();
con.closePath();
}
And then use it like this:
bezierCurve(x + 60, y + 75, 80, 130);
There are a couple use examples in the fiddle, along with a failed attempt to make one using quadraticCurveTo.
I like the Bezier curves solution above. I noticed the scale also affects the line width so if you're trying to draw an ellipse that is wider than it is tall, your top and bottom "sides" will appear thinner than your left and right "sides"...
a good example would be:
ctx.lineWidth = 4;
ctx.scale(1, 0.5);
ctx.beginPath();
ctx.arc(20, 20, 10, 0, Math.PI * 2, false);
ctx.stroke();
you should notice the width of the line at the peak and valley of the ellipse are half as wide as at the left and right apexes (apices?).
Chrome and Opera support ellipse method for canvas 2d context, but IE,Edge,Firefox and Safari don't support it.
We can implement the ellipse method by JS or use a third-party polyfill.
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
Usage example:
ctx.ellipse(20, 21, 10, 10, 0, 0, Math.PI*2, true);
You can use a canvas-5-polyfill to provide ellipse method.
Or just paste some js code to provide ellipse method:
if (CanvasRenderingContext2D.prototype.ellipse == undefined) {
CanvasRenderingContext2D.prototype.ellipse = function(x, y, radiusX, radiusY,
rotation, startAngle, endAngle, antiClockwise) {
this.save();
this.translate(x, y);
this.rotate(rotation);
this.scale(radiusX, radiusY);
this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
this.restore();
}
}
Yes, it is possible with two bezier curves - here's a brief tutorial/example:
http://www.williammalone.com/briefs/how-to-draw-ellipse-html5-canvas/
Since nobody came up with an approach using the simpler quadraticCurveTo I am adding a solution for that. Simply replace the bezierCurveTo calls in the #Steve's answer with this:
ctx.quadraticCurveTo(x,y,xm,y);
ctx.quadraticCurveTo(xe,y,xe,ym);
ctx.quadraticCurveTo(xe,ye,xm,ye);
ctx.quadraticCurveTo(x,ye,x,ym);
You may also remove the closePath. The oval is looking slightly different though.
This is another way of creating an ellipse like shape, although it uses the "fillRect()" function this can be used be changing the arguments in the fillRect() function.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sine and cosine functions</title>
</head>
<body>
<canvas id="trigCan" width="400" height="400"></canvas>
<script type="text/javascript">
var canvas = document.getElementById("trigCan"), ctx = canvas.getContext('2d');
for (var i = 0; i < 360; i++) {
var x = Math.sin(i), y = Math.cos(i);
ctx.stroke();
ctx.fillRect(50 * 2 * x * 2 / 5 + 200, 40 * 2 * y / 4 + 200, 10, 10, true);
}
</script>
</body>
</html>
With this you can even draw segments of an ellipse:
function ellipse(color, lineWidth, x, y, stretchX, stretchY, startAngle, endAngle) {
for (var angle = startAngle; angle < endAngle; angle += Math.PI / 180) {
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + Math.cos(angle) * stretchX, y + Math.sin(angle) * stretchY)
ctx.lineWidth = lineWidth
ctx.strokeStyle = color
ctx.stroke()
ctx.closePath()
}
}
http://jsfiddle.net/FazAe/1/
Here's a function I wrote that uses the same values as the ellipse arc in SVG. X1 & Y1 are the last coordinates, X2 & Y2 are the end coordinates, radius is a number value and clockwise is a boolean value. It also assumes your canvas context has already been defined.
function ellipse(x1, y1, x2, y2, radius, clockwise) {
var cBx = (x1 + x2) / 2; //get point between xy1 and xy2
var cBy = (y1 + y2) / 2;
var aB = Math.atan2(y1 - y2, x1 - x2); //get angle to bulge point in radians
if (clockwise) { aB += (90 * (Math.PI / 180)); }
else { aB -= (90 * (Math.PI / 180)); }
var op_side = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) / 2;
var adj_side = Math.sqrt(Math.pow(radius, 2) - Math.pow(op_side, 2));
if (isNaN(adj_side)) {
adj_side = Math.sqrt(Math.pow(op_side, 2) - Math.pow(radius, 2));
}
var Cx = cBx + (adj_side * Math.cos(aB));
var Cy = cBy + (adj_side * Math.sin(aB));
var startA = Math.atan2(y1 - Cy, x1 - Cx); //get start/end angles in radians
var endA = Math.atan2(y2 - Cy, x2 - Cx);
var mid = (startA + endA) / 2;
var Mx = Cx + (radius * Math.cos(mid));
var My = Cy + (radius * Math.sin(mid));
context.arc(Cx, Cy, radius, startA, endA, clockwise);
}
If you want the ellipse to fully fit inside a rectangle, it's really like this:
function ellipse(canvasContext, x, y, width, height){
var z = canvasContext, X = Math.round(x), Y = Math.round(y), wd = Math.round(width), ht = Math.round(height), h6 = Math.round(ht/6);
var y2 = Math.round(Y+ht/2), xw = X+wd, ym = Y-h6, yp = Y+ht+h6, cs = cards, c = this.card;
z.beginPath(); z.moveTo(X, y2); z.bezierCurveTo(X, ym, xw, ym, xw, y2); z.bezierCurveTo(xw, yp, X, yp, X, y2); z.fill(); z.stroke();
return z;
}
Make sure your canvasContext.fillStyle = 'rgba(0,0,0,0)'; for no fill with this design.

Categories

Resources