Canvas fill color bug in chrome - javascript

Here's a picture of the issue. I'm drawing this color wheel, and using
var context = canvas.getContext("2d");
...
context.fillStyle = "rgba(" + r + ", " + g + ", " + b + ", 1)";
context.fill();
for each little differently colored section in the wheel.
the order of the browsers here are:
Chrome | Firefox | IE
And for whatever reason it looks all messed up in Chrome. I'm not even really sure how to properly describe this issue so it's hard to look to see if this is a known issue.
edit: here's a fiddle https://jsfiddle.net/mattlokk/7whrmo8r/3/
edit 2: seems to only be happening in Chrome version 58.x, and it seems to work fine on some machines no matter what Chrome version.

Workaround
Yes there does seam to be a bug with the arc function in chrome (I used Canary 60)
1st workaround
As a bug is not something you can solve you need to use a workaround. In this case a simple solution is to create a shadow function for arc.
// x,y center of arc
// r is radius
// start and end are in radians
// dir (optional) if truthy true draw path anti clock wise.
// returns undefined;
function arcFix (x, y, r, start, end, dir) {
var ang;
var step = 1 / r;
if (dir) {
end -= step / 2; // end bumped down by half a step to ensure rounding error does not miss end
for (ang = start; ang > end; ang -= step) {
context.lineTo(
Math.cos(ang) * r + x,
Math.sin(ang) * r + y,
);
}
} else {
end += step / 2; // end bumped up half a step to ensure rounding error does not miss end
for (ang = start; ang < end; ang += step) {
context.lineTo(
Math.cos(ang) * r + x,
Math.sin(ang) * r + y,
);
}
}
}
Add the above function and in the code where you render each colour segment replace the two ctx.arc calls calling the above function
context.beginPath();
context.moveTo(innerX1, innerY1);
context.lineTo(outerX1, outerY1);
// use shadow function
arcFix(centerX, centerY, (outerRadius - 1), startRadians, endRadians);
context.lineTo(innerX2, innerY2);
// use shadow function
arcFix(centerX, centerY, (innerRadius + 1), endRadians, startRadians, true);
Please note that the arcFix function is not a complete replacement for arc and has only been tested for this particular situation. You should test it (as you should test all code) if you use it else where.
2nd workaround
Removing the +1 and -1 from the radius fixes the problem as well. Not that you have incorrectly used the 2D context as the effect is creating a alpha gradient and that should not happen no matter where you add path segments.
context.lineTo(outerX1, outerY1);
context.arc(centerX, centerY, outerRadius , startRadians, endRadians);
// ^ remove - 1
context.lineTo(innerX2, innerY2);
context.arc(centerX, centerY, innerRadius , endRadians, startRadians, true);
// ^ remove + 1

Related

Rotating an image to face mouse point using ATAN instead of ATAN2

In order to better understand how trigonometry works in game development, I've been creating little javascript snippets on CodePen.
I managed to create an example that uses Math.atan2() to point a pixel-art shotgun at the mouse cursor.
Now, I am trying to accomplish the same exact thing using the Math.atan() function but it isn't functioning properly.
Here is the logic code I am using:
canvas.onmousemove = function(event) {
Mouse = {
x: event.pageX,
y: event.pageY
}
// These length variables use the distance formula
var opposite_length = Math.sqrt((Mouse.x - Mouse.x) * (Mouse.x - Mouse.x) + (Mouse.y - y) * (Mouse.y - y));
var adj_length = Math.sqrt((Mouse.x - x) * (Mouse.x - x) + (y - y) * (y - y));
var angle_in_radians = Math.atan(opposite_length / adj_length);
//var angle_in_radians = Math.atan2(Mouse.y - y, Mouse.x - x);
angle = angle_in_radians * (180 / Math.PI);
}
The in my draw() function, I rotate the gun to the angle var using:
cxt.rotate(angle*(Math.PI/180));
If you uncomment the line that starts as // var angle_in_radians, everything will suddenly work.
So, atan2 is working, but atan is producing the result I want.
I know that opposite_length and adj_length are accurate, because when i console.log() them, they are the correct values.
You can check out the code being used on CodePen for a live example.
There's a lot of initialization stuff but you only really need to focus on the canvas.onmousemove = function(event) section, starting on line 50. You can also check out my draw function on line 68.
Note that your atan computation is equivalent to
atan2( abs(mouse.y-y), abs(mouse.x-x) )
The screen coordinates have the opposite orientation to the cartesian coordinates. To get a cartesian angle from screen coordinates, use
atan2( y-mouse.y, mouse.x-x )

calculating intersection point of quadratic bezier curve

This is definitely pushing the limits for my trig knowledge.
Is there a formula for calculating an intersection point between a quadratic bezier curve and a line?
Example:
in the image below, I have P1, P2, C (which is the control point) and X1, X2 (which for my particular calculation is just a straight line on the X axis.)
What I would like to be able to know is the X,Y position of T as well as the angle of the tangent at T. at the intersection point between the red curve and the black line.
After doing a little research and finding this question, I know I can use:
t = 0.5; // given example value
x = (1 - t) * (1 - t) * p[0].x + 2 * (1 - t) * t * p[1].x + t * t * p[2].x;
y = (1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y;
to calculate my X,Y position at any given point along the curve. So using that I could just loop through a bunch of points along the curve, checking to see if any are on my intersecting X axis. And from there try to calculate my tangent angle. But that really doesn't seem like the best way to do it. Any math guru's out there know what the best way is?
I'm thinking that perhaps it's a bit more complicated than I want it to be.
If you only need an intersection with a straight line in the x-direction you already know the y-coordinate of the intersection. To get the x-coordinate do something like this:
The equation for your line is simply y = b
Setting it equal to your y-equation of the beziér function y(t) gets you:
b = (1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y
Solving* for t gets you:
t = (p[0].y - p[1].y - sqrt(b*a + p[1].y*p[1].y - p[0].y*p[2].y)) / a
with a = p[0].y - 2*p[1].y + p[2].y
Insert the resulting t into your x-equation of the beziér function x(t) to get the x-coordinate and you're done.
You may have to pay attention to some special cases, like when no solution exists, because the argument of the square root may then become negative or the denominator (a) might become zero, or something like that.
Leave a comment if you need more help or the intersection with arbitrary lines.
(*) I used wolfram alpha to solve the equation because I'm lazy: Wolfram alpha solution.
Quadratic curve formula:
y=ax^2+bx+c // where a,b,c are known
Line formula:
// note: this `B` is not the same as the `b` in the quadratic formula ;-)
y=m*x+B // where m,B are known.
The curve & line intersect where both equations are true for the same [x,y]:
Here's annotated code and a Demo:
// canvas vars
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// linear interpolation utility
var lerp=function(a,b,x){ return(a+x*(b-a)); };
// qCurve & line defs
var p1={x:125,y:200};
var p2={x:250,y:225};
var p3={x:275,y:100};
var a1={x:30,y:125};
var a2={x:300,y:175};
// calc the intersections
var points=calcQLintersects(p1,p2,p3,a1,a2);
// plot the curve, line & solution(s)
var textPoints='Intersections: ';
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.quadraticCurveTo(p2.x,p2.y,p3.x,p3.y);
ctx.moveTo(a1.x,a1.y);
ctx.lineTo(a2.x,a2.y);
ctx.stroke();
ctx.beginPath();
for(var i=0;i<points.length;i++){
var p=points[i];
ctx.moveTo(p.x,p.y);
ctx.arc(p.x,p.y,4,0,Math.PI*2);
ctx.closePath();
textPoints+=' ['+parseInt(p.x)+','+parseInt(p.y)+']';
}
ctx.font='14px verdana';
ctx.fillText(textPoints,10,20);
ctx.fillStyle='red';
ctx.fill();
///////////////////////////////////////////////////
function calcQLintersects(p1, p2, p3, a1, a2) {
var intersections=[];
// inverse line normal
var normal={
x: a1.y-a2.y,
y: a2.x-a1.x,
}
// Q-coefficients
var c2={
x: p1.x + p2.x*-2 + p3.x,
y: p1.y + p2.y*-2 + p3.y
}
var c1={
x: p1.x*-2 + p2.x*2,
y: p1.y*-2 + p2.y*2,
}
var c0={
x: p1.x,
y: p1.y
}
// Transform to line
var coefficient=a1.x*a2.y-a2.x*a1.y;
var a=normal.x*c2.x + normal.y*c2.y;
var b=(normal.x*c1.x + normal.y*c1.y)/a;
var c=(normal.x*c0.x + normal.y*c0.y + coefficient)/a;
// solve the roots
var roots=[];
d=b*b-4*c;
if(d>0){
var e=Math.sqrt(d);
roots.push((-b+Math.sqrt(d))/2);
roots.push((-b-Math.sqrt(d))/2);
}else if(d==0){
roots.push(-b/2);
}
// calc the solution points
for(var i=0;i<roots.length;i++){
var minX=Math.min(a1.x,a2.x);
var minY=Math.min(a1.y,a2.y);
var maxX=Math.max(a1.x,a2.x);
var maxY=Math.max(a1.y,a2.y);
var t = roots[i];
if (t>=0 && t<=1) {
// possible point -- pending bounds check
var point={
x:lerp(lerp(p1.x,p2.x,t),lerp(p2.x,p3.x,t),t),
y:lerp(lerp(p1.y,p2.y,t),lerp(p2.y,p3.y,t),t)
}
var x=point.x;
var y=point.y;
// bounds checks
if(a1.x==a2.x && y>=minY && y<=maxY){
// vertical line
intersections.push(point);
}else if(a1.y==a2.y && x>=minX && x<=maxX){
// horizontal line
intersections.push(point);
}else if(x>=minX && y>=minY && x<=maxX && y<=maxY){
// line passed bounds check
intersections.push(point);
}
}
}
return intersections;
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<h4>Calculate intersections of QBez-Curve and Line</h4>
<canvas id="canvas" width=350 height=350></canvas>
calculate line's tangθ with x-coordinate
then intersection of the curve's (x, y) should be the same tangθ
so solution is
a = line's x distance from (line.x,0) to (0,0)
(curve.x + a) / curve.y = tangθ (θ can get from the line intersection with x-coordidate)

Canvas water/blob physics on a circular path with texture?

this is my first question after having relied on this site for years!
Anyway, I'd like to accomplish something similar to this effect:
http://www.flashmonkey.co.uk/html5/wave-physics/
But on a circular path, instead of a horizon. Essentially, a floating circle/blob in the center of the screen that would react to mouse interaction. What I'm not looking for is gravity, or for the circle to bounce around the screen - only surface ripples.
If at all possible I'd like to apply a static texture to the shape, is this a possibility? I'm completely new to Canvas!
I've already tried replacing some code from the above example with circular code from the following link, to very limited success:
http://www.html5canvastutorials.com/tutorials/html5-canvas-circles/
If only it were that easy :)
Any ideas?
Thanks in advance!
I tried to figure out how wave simulation works using View Source and JavaScript console. It's working fine but threw some JS errors. Also, it seems physics update is entangled with rendering in the render() method.
Here is what I found about the code:
The mouseMove() method creates disturbances on the wave based on mouse position, creating a peak around the mouse. The target variable is the index of the particle that needs to be updated, it's calculated from mouse pos.
if (particle && mouseY > particle.y) {
var speed = mouseY - storeY;
particles[target - 2].vy = speed / 6;
particles[target - 1].vy = speed / 5;
particles[target].vy = speed / 3;
particles[target + 1].vy = speed / 5;
particles[target + 2].vy = speed / 6;
storeY = mouseY;
}
Then, the particles around target are updated. The problem I found is that it does no bounds checking, i.e. it can potentially particles[-1] when target == 0. If that happens, an exception is thrown, the method call ends, but the code does not stop.
The render() method first updates the particle positions, then renders the wave.
Here is its physics code:
for (var u = particles.length - 1; u >= 0; --u) {
var fExtensionY = 0;
var fForceY = 0;
if (u > 0) {
fExtensionY = particles[u - 1].y - particles[u].y - springs[u - 1].iLengthY;
fForceY += -fK * fExtensionY;
}
if (u < particles.length - 1) {
fExtensionY = particles[u].y - particles[u + 1].y - springs[u].iLengthY;
fForceY += fK * fExtensionY;
}
fExtensionY = particles[u].y - particles[u].origY;
fForceY += fK / 15 * fExtensionY;
particles[u].ay = -fForceY / particles[u].mass;
particles[u].vy += particles[u].ay;
particles[u].ypos += particles[u].vy;
particles[u].vy /= 1.04;
}
Basically, it's Hooke's Law for a chain of particles linked by springs between them. For each particle u, it adds the attraction to the previous and next particles (the if statements check if they are available), to the variable fForceY. I don't fully understand the purpose of the springs array.
In the last four lines, it calculates the acceleration (force / mass), updates the velocity (add acceleration), then position (add velocity), and finally, reduce velocity by 1.04 (friction).
After the physics update, the code renders the wave:
context.clearRect(0, 0, stageWidth, stageHeight);
context.fillStyle = color;
context.beginPath();
for (u = 0; u < particles.length; u++) {
...
}
...
context.closePath();
context.fill();
I'm not explaining that, you need to read a canvas tutorial to understand it.
Here are some ideas to get started, note that I didn't test these code.
To modify the code to draw a circular wave, we need introduce a polar coordinate system, where the particle's x-position is the angle in the circle and y-position the distance from center. We should use theta and r here but it requires a large amount of refactoring. We will talk about transforming later.
mouseMove(): Compute particle index from mouse position on screen to polar coordinates, and make sure the disturbance wrap around:
Define the function (outside mouseMove(), we need this again later)
function wrapAround(i, a) { return (i + a.length) % a.length; }
Then change
particles[target - 2] --> particles[wrapAround(target - 2, particles)]
particles[target - 1] --> particles[wrapAround(target - 1, particles)]
...
The modulo operator does the job but I added particles.length so I don't modulo a negative number.
render(): Make sure the force calculation wrap around, so we need to wrapAround function again. We can strip away the two if statements:
fExtensionY = particles[wrapAround(u - 1, particles)].y - particles[u].y - springs[wrapAround(u - 1, springs)].iLengthY;
fForceY += -fK * fExtensionY;
fExtensionY = particles[u].y - particles[wrapAround(u + 1, particles)].y - springs[warpAround(u, springs)].iLengthY;
fForceY += fK * fExtensionY;
Here is the result so far in jsfiddle: Notice the wave propagate from the other side. http://jsfiddle.net/DM68M/
After that's done, the hardest part is rendering them on a circle. To do that, we need coordinate transform functions that treat particle's (x, y) as (angle in the circle, distance from center), and we also need inverse transforms for mouse interaction in mouseMove().
function particleCoordsToScreenCoords(particleX, particleY) {
return [ radiusFactor * particleY * Math.cos(particleX / angleFactor),
radiusFactor * particleY * Math.sin(particleX / angleFactor) ];
}
function screenCoordsToParticleCoords(screenX, screenY) {
// something involving Math.atan2 and Math.sqrt
}
Where the ...Factor variables needed to be determined separately. The angleFactor is two pi over the highest x-position found among particles array
Then, in the coordinates supplied to the context.lineTo, context.arc, use the particleCoordsToScreenCoords to transform the coordinates.

Handdrawn circle simulation in HTML 5 canvas

The following code creates a circle in HTML 5 Canvas using jQuery:
Code:
//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");
DrawCircle(75, 75, 20);
//draw a circle
function DrawCircle(x, y, radius)
{
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2, true);
ctx.fillStyle = 'transparent';
ctx.lineWidth = 2;
ctx.strokeStyle = '#003300';
ctx.stroke();
ctx.closePath();
ctx.fill();
}
I am trying to simulate any of the following types of circles:
I have researched and found this article but was unable to apply it.
I would like for the circle to be drawn rather than just appear.
Is there a better way to do this? I'm sensing there's going to be a lot of math involved :)
P.S. I like the simplicity of PaperJs, maybe this would be the easiest approach using it's simplified paths?
There are already good solutions presented here. I wanted to add a variations of what is already presented - there are not many options beyond some trigonometry if one want to simulate hand drawn circles.
I would first recommend to actually record a real hand drawn circle. You can record the points as well as the timeStamp and reproduce the exact drawing at any time later. You could combine this with a line smoothing algorithm.
This here solution produces circles such as these:
You can change color, thickness etc. by setting the strokeStyle, lineWidth etc. as usual.
To draw a circle just call:
handDrawCircle(context, x, y, radius [, rounds] [, callback]);
(callback is provided as the animation makes the function asynchronous).
The code is separated into two segments:
Generate the points
Animate the points
Initialization:
function handDrawCircle(ctx, cx, cy, r, rounds, callback) {
/// rounds is optional, defaults to 3 rounds
rounds = rounds ? rounds : 3;
var x, y, /// the calced point
tol = Math.random() * (r * 0.03) + (r * 0.025), ///tolerance / fluctation
dx = Math.random() * tol * 0.75, /// "bouncer" values
dy = Math.random() * tol * 0.75,
ix = (Math.random() - 1) * (r * 0.0044), /// speed /incremental
iy = (Math.random() - 1) * (r * 0.0033),
rx = r + Math.random() * tol, /// radius X
ry = (r + Math.random() * tol) * 0.8, /// radius Y
a = 0, /// angle
ad = 3, /// angle delta (resolution)
i = 0, /// counter
start = Math.random() + 50, /// random delta start
tot = 360 * rounds + Math.random() * 50 - 100, /// end angle
points = [], /// the points array
deg2rad = Math.PI / 180; /// degrees to radians
In the main loop we don't bounce around randomly but increment with a random value and then increment linearly with that value, reverse it if we are at bounds (tolerance).
for (; i < tot; i += ad) {
dx += ix;
dy += iy;
if (dx < -tol || dx > tol) ix = -ix;
if (dy < -tol || dy > tol) iy = -iy;
x = cx + (rx + dx * 2) * Math.cos(i * deg2rad + start);
y = cy + (ry + dy * 2) * Math.sin(i * deg2rad + start);
points.push(x, y);
}
And in the last segment we just render what we have of points.
The speed is determined by da (delta angle) in the previous step:
i = 2;
/// start line
ctx.beginPath();
ctx.moveTo(points[0], points[1]);
/// call loop
draw();
function draw() {
ctx.lineTo(points[i], points[i + 1]);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(points[i], points[i + 1]);
i += 2;
if (i < points.length) {
requestAnimationFrame(draw);
} else {
if (typeof callback === 'function')
callback();
}
}
}
Tip: To get a more realistic stroke you can reduce globalAlpha to for example 0.7.
However, for this to work properly you need to draw solid to an off-screen canvas first and then blit that off-screen canvas to main canvas (which has the globalAlpha set) for each frame or else the strokes will overlap between each point (which does not look good).
For squares you can use the same approach as with the circle but instead of using radius and angle you apply the variations to a line. Offset the deltas to make the line non-straight.
I tweaked the values a little but feel free to tweak them more to get a better result.
To make the circle "tilt" a little you can first rotate the canvas a little:
rotate = Math.random() * 0.5;
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(-rotate);
ctx.translate(-cx, -cy);
and when the loop finishes:
if (i < points.length) {
requestAnimationFrame(draw);
} else {
ctx.restore();
}
(included in the demo linked above).
The circle will look more like this:
Update
To deal with the issues mentioned (comment fields too small :-) ): it's actually a bit more complicated to do animated lines, especially in a case like this where you a circular movement as well as a random boundary.
Ref. comments point 1: the tolerance is closely related to radius as it defined max fluctuation. We can modify the code to adopt a tolerance (and ix/iy as they defines how "fast" it will variate) based on radius. This is what I mean by tweaking, to find that value/sweet-spot that works well with all sizes. The smaller the circle the smaller the variations. Optionally specify these values as arguments to the function.
Point 2: since we're animating the circle the function becomes asynchronous. If we draw two circles right after each other they will mess up the canvas as seen as new points are added to the path from both circles which then gets stroked criss-crossed.
We can get around this by providing a callback mechanism:
handDrawCircle(context, x, y, radius [, rounds] [, callback]);
and then when the animation has finished:
if (i < points.length) {
requestAnimationFrame(draw);
} else {
ctx.restore();
if (typeof callback === 'function')
callback(); /// call next function
}
Another issues one will run into with the code as-is (remember that the code is meant as an example not a full solution :-) ) is with thick lines:
When we draw segment by segment separately canvas does not know how to calculate the butt angle of the line in relation to previous segment. This is part of the path-concept. When you stroke a path with several segments canvas know at what angle the butt (end of the line) will be at. So here we to either draw the line from start to current point and do a clear in between or only small lineWidth values.
When we use clearRect (which will make the line smooth and not "jaggy" as when we don't use a clear in between but just draw on top) we would need to consider implementing a top canvas to do the animation with and when animation finishes we draw the result to main canvas.
Now we start to see part of the "complexity" involved. This is of course because canvas is "low-level" in the sense that we need to provide all logic for everything. We are basically building systems each time we do something more with canvas than just draw simple shapes and images (but this also gives the great flexibility).
Here are some basics I created for this answer:
http://jsfiddle.net/Exceeder/TPDmn/
Basically, when you draw a circle, you need to account for hand imperfections. So, in the following code:
var img = new Image();
img.src="data:image/png;base64,...";
var ctx = $('#sketch')[0].getContext('2d');
function draw(x,y) {
ctx.drawImage(img, x, y);
}
for (var i=0; i<500; i++) {
var radiusError = +10 - i/20;
var d = 2*Math.PI/360 * i;
draw(200 + 100*Math.cos(d), 200 + (radiusError+80)*Math.sin(d) );
}
Pay attention how vertical radiusError changes when the angle (and the position) grows. You are welcome to play with this fiddle until you get a "feel" what component does what. E.g. it would make sense to introduce another component to radiusError that emulates "unsteady" hand by slowly changing it my random amounts.
There are many different ways to do this. I choose trig functions for the simplicity of the simulation, as speed is not a factor here.
Update:
This, for example, will make it less perfect:
var d = 2*Math.PI/360 * i;
var radiusError = +10 - i/20 + 10*Math.sin(d);
Obviously, the center of the circle is at (200,200), as the formula for drawing a circle (rather, ellipsis with vertical radius RY and horizontal radius RX) with trigonometric functions is
x = centerX + RX * cos ( angle )
y = centerY + RY * sin ( angle )
Your task seems to have 3 requirements:
A hand-drawn shape.
An “organic” rather than “ultra-precise” stroke.
Revealing the circle incrementally instead of all-at-once.
To get started, check out this nice on-target demo by Andrew Trice.
This amazing circle is hand drawn by me (you can laugh now...!)
Andrew's demo does steps 1 and 2 of your requirements.
It lets you hand draw a circle (or any shape) using an organic looking “brush effect” instead of the usual ultra-precise lines normally used in canvas.
It achieves the “brush effect” by by repeated drawing a brush image between hand drawn points
Here’s the demo:
http://tricedesigns.com/portfolio/sketch/brush.html#
And the code is available on GitHub:
https://github.com/triceam/HTML5-Canvas-Brush-Sketch
Andrew Trice’s demo draws-and-forgets the lines that make up your circle.
Your task would be to impliment your third requirement (remembering strokes):
Hand draw a circle of your own,
Save each line segment that makes up your circle in an array,
“Play” those segements using Andrew’s stylized brush technique.
Results: A hand-drawn and stylized circle that appears incrementally instead of all at once.
You have an interesting project…If you feel generous, please share your results!
See live demo here. Also available as a gist.
<div id="container">
<svg width="100%" height="100%" viewBox='-1.5 -1.5 3 3'></svg>
</div>
#container {
width:500px;
height:300px;
}
path.ln {
stroke-width: 3px;
stroke: #666;
fill: none;
vector-effect: non-scaling-stroke;
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
-webkit-animation: dash 5s ease-in forwards;
-moz-animation:dash 5s ease-in forwards;
-o-animation:dash 5s ease-in forwards;
animation:dash 5s ease-in forwards;
}
#keyframes dash {
to { stroke-dashoffset: 0; }
}
function path(δr_min,δr_max, el0_min, el0_max, δel_min,δel_max) {
var c = 0.551915024494;
var atan = Math.atan(c)
var d = Math.sqrt( c * c + 1 * 1 ), r = 1;
var el = (el0_min + Math.random() * (el0_max - el0_min)) * Math.PI / 180;
var path = 'M';
path += [r * Math.sin(el), r * Math.cos(el)];
path += ' C' + [d * r * Math.sin(el + atan), d * r * Math.cos(el + atan)];
for (var i = 0; i < 4; i++) {
el += Math.PI / 2 * (1 + δel_min + Math.random() * (δel_max - δel_min));
r *= (1 + δr_min + Math.random()*(δr_max - δr_min));
path += ' ' + (i?'S':'') + [d * r * Math.sin(el - atan), d * r * Math.cos(el - atan)];
path += ' ' + [r * Math.sin(el), r * Math.cos(el)];
}
return path;
}
function cX(λ_min, λ_max, el_min, el_max) {
var el = (el_min + Math.random()*(el_max - el_min));
return 'rotate(' + el + ') ' + 'scale(1, ' + (λ_min + Math.random()*(λ_max - λ_min)) + ')'+ 'rotate(' + (-el) + ')';
}
function canvasArea() {
var width = Math.floor((Math.random() * 500) + 450);
var height = Math.floor((Math.random() * 300) + 250);
$('#container').width(width).height(height);
}
d3.selectAll( 'svg' ).append( 'path' ).classed( 'ln', true) .attr( 'd', path(-0.1,0, 0,360, 0,0.2 )).attr( 'transform', cX( 0.6, 0.8, 0, 360 ));
setTimeout(function() { location = '' } ,5000)

Canvas ArcTo confusion

So I am once again dealing with annular sectors which is not my forte. I can use the .arc method on canvas just fine, the problem comes from needing my arc to be part of a path.
So for example:
ctx.save();
ctx.arc(centerX, centerY, radius, startAngle, endAngle, true);
ctx.stroke();
ctx.restore();
Works fine. But now I need it as part of a path, so I have something like this:
var pointArray = [...]; //this contains all four corner points of the annular sector
ctx.save();
ctx.moveTo(pointArray[0].x, pointArray[0].y);
ctx.lineTo(pointArray[1].x, pointArray[1].y); //so that draws one of the flat ends
ctx.arcTo(?, ?, pointArray[2].x pointArray[2].y, radius);
That tangent of tangent coordinate is driving me mad. Plus I have a serious concern:
http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html
Makes it sound like an arc drawn with arcTo could never cover 180degrees or more of a circle and there will be times that my annular sector will be greater than 180degrees.
Thanks for the help superior geometric minds of stackoverflow!
UPDATE
Ok, so I am having to do svg canvas inter-polarity here, and using coffee-script, actual production code follows!
annularSector : (startAngle,endAngle,innerRadius,outerRadius) ->
startAngle = degreesToRadians startAngle+180
endAngle = degreesToRadians endAngle+180
p = [
[ #centerX+innerRadius*Math.cos(startAngle), #centerY+innerRadius*Math.sin(startAngle) ]
[ #centerX+outerRadius*Math.cos(startAngle), #centerY+outerRadius*Math.sin(startAngle) ]
[ #centerX+outerRadius*Math.cos(endAngle), #centerY+outerRadius*Math.sin(endAngle) ]
[ #centerX+innerRadius*Math.cos(endAngle), #centerY+innerRadius*Math.sin(endAngle) ]
]
angleDiff = endAngle - startAngle
largeArc = (if (angleDiff % (Math.PI * 2)) > Math.PI then 1 else 0)
if #isSVG
commands = []
commands.push "M" + p[0].join()
commands.push "L" + p[1].join()
commands.push "A" + [ outerRadius, outerRadius ].join() + " 0 " + largeArc + " 1 " + p[2].join()
commands.push "L" + p[3].join()
commands.push "A" + [ innerRadius, innerRadius ].join() + " 0 " + largeArc + " 0 " + p[0].join()
commands.push "z"
return commands.join(" ")
else
#gaugeCTX.moveTo p[0][0], p[0][1]
#gaugeCTX.lineTo p[1][0], p[1][1]
##gaugeCTX.arcTo
#gaugeCTX.arc #centerX, #centerY, outerRadius, startAngle, endAngle, false
##gaugeCTX.moveTo p[2][0], p[2][1]
#gaugeCTX.lineTo p[3][0], p[3][1]
#gaugeCTX.arc #centerX, #centerY, innerRadius, startAngle, endAngle, false
THE SOLUTION
#gaugeCTX.moveTo p[0][0], p[0][1]
#gaugeCTX.lineTo p[1][0], p[1][1]
#gaugeCTX.arc #centerX, #centerY, outerRadius, startAngle, endAngle, false
#gaugeCTX.lineTo p[3][0], p[3][1]
#gaugeCTX.arc #centerX, #centerY, innerRadius, endAngle, startAngle, true #note that this arc is set to true and endAngle and startAngle are reversed!
I recently found myself disappointed by the arcTo() method (which really should have been called roundedCorner() ). I decided to come up with a general-purpose work-around for people who want to use the cx,cy,r,theta1,theta2 expression as well:
http://www.purplefrog.com/~thoth/art/paisley/arcTo.html
With the important bit of code copied in here:
/**
if code is "move" then we will do a moveTo x0,y0
if code is "line" then we will do a lineTo x0,y0
if code is anything else, we'll assume the cursor is already at x0,y0
*/
function otherArcTo(ctx, cx, cy, r, theta1, theta2, code)
{
console.log([cx,cy,r,theta1, theta2, code])
var x0 = cx + r*Math.cos(theta1)
var y0 = cy + r*Math.sin(theta1)
if (code=="move") {
ctx.moveTo(x0,y0)
} else if (code=="line") {
ctx.lineTo(x0,y0)
}
var dTheta = theta2-theta1
var nChunks = Math.ceil( Math.abs(dTheta) / (0.67*Math.PI) )
if (nChunks <=1) {
var theta3 = theta1 + dTheta/2
var r3 = r/Math.cos(dTheta/2)
var x1 = cx + r3*Math.cos(theta3)
var y1 = cy + r3*Math.sin(theta3)
var x2 = cx + r*Math.cos(theta2)
var y2 = cy + r*Math.sin(theta2)
ctx.arcTo(x1,y1,x2,y2, r)
} else {
for (var i=0; i<nChunks; i++) {
var code2 = null
if (i==0)
code2 = code
otherArcTo(ctx, cx, cy, r,
theta1 + dTheta*i/nChunks,
theta1 + dTheta*(i+1)/nChunks, code2)
}
}
}
While your question/code is not 100% clear to me,
arcTo() still has browser/rendering problems, so use arc() for now.
(Please forgive me, I can't give detailed link right now since I to became a victim of new forced firefox 12 crap and lost years of notes in my ff3.6 powered system, which it simply deleted during it's unapproved update).
arc() works with radians, so do a quick read-up on wiki how Math.PI relates to radians, then whip up some formula to convert your degrees (or what ever you might wish) to radians.
You'll be doing something like: (((2 * Math.PI) / 360) * 270)    (=3/4 circle)
By the way: I did not ran into noticeable drawing problems with Radian/Unit conversions and ECMAscript's floating point behavior!
Also don't forget beginPath() and closePath() (and stroke() where needed): don't make the canvas guess!! This is usually key to drawing (closed) paths!!
You might also want to look at bezierCurveTo().
UPDATE (on TS' update):
Looking at your picture (which I guess is the rendering of your problem), I think I see what you want: pieces of pie-charts.
This is simple, they are 2 arcs and two lines between a beginPath() and closePath() (and a fill-color).
What you want to do is to center your origin (point 0,0) with translate(). Before you do this, read-up on getting crisp lines: the trick is to translate to half pixels: (x.5,y.5).
Then make one 'main-canvas' and one 'temp-canvas'. For each piece-of-pie, draw a on clean temp-canvas (just set it's width and height instead of some clear mumbo jumbo) and put this temp-canvas on your main-canvas. Lastly render/output your main-canvas. Done.
The 'magic' (plain math) in your script that does the translation between your existing svg-path I cannot help you with, since I('m ashamed to admit) don't recognize any javascript in your updated source.
Hope this helps!
Update 2: Actually.. if you would tell us the format of your points/coordinates array.. that would really help! Then we would know from where to where you are drawing.
The best solution MIGHT indeed be to whip-up a javascript function that accepts your points-array's..
This way your coffeescript could simply spit-out your known values (data-format) to the javascript that was needed anyways to render canvas (in html).
Which makes me think.. there must be existing svg-path to canvas translation-scripts .. right? Maybe some-one knows of a tried and tested one and can link/copy it here (for future reference)..
Update 3:
HINT: Don't forget: you can rotate the canvas in drawing-mode, but also when layering canvas'.
When you rotate (which works with the same radian-principle mentioned above) the canvas will rotate around it's origin-point (0,0) which is why translating (to the center of the canvas + 0.5px) is so useful for drawing these kind circle-based shapes!!!
I was having trouble with this myself. Once I drew it out on a piece of paper and used a little bit of geometry and trigonometry, it was pretty simple.
This function will help you calculate the points you need for the arcTo() function. You can move (translate) the arc by adding/subtracting from from the x and y at each point.
function calculateArcPoints(radius, rotation, sectionAngle) {
var halfSectionAngle = sectionAngle / 2;
return {
control: {
x: Math.cos(rotation) * radius / Math.cos(halfSectionAngle),
y: -1 * Math.sin(rotation) * radius / Math.cos(halfSectionAngle)
},
start: {
x: Math.cos(rotation - halfSectionAngle) * radius,
y: -1 * Math.sin(rotation - halfSectionAngle) * radius
},
end: {
x: Math.cos(rotation + halfSectionAngle) * radius,
y: -1 * Math.sin(rotation + halfSectionAngle) * radius
}
};
}
I used KineticJS and no SVG or coffee-script, so the rotation and translation was done outside the drawing function. Here's the full code on jsFiddle. I drew out multiple annular sections around a circle, but you can easily modify it to only draw one. Basically, you have an inner radius, an outer radius, and you connect each of them with straight lines at their start and end points.
That tangent of tangent coordinate is driving me mad. Plus I have a serious concern: http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html Makes it sound like an arc drawn with arcTo could never cover 180degrees or more of a circle and there will be times that my annular sector will be greater than 180degrees.
You are correct about the arcTo() function. It can only produce arcs that are less than 180 degrees. Tangent lines at >= 180° will never intersect, so there cannot be a control point for the arcTo() function. You could just draw two or (I would do three for an entire annulus) more adjacent to each other.

Categories