HTML Canvas - Dotted stroke around circle - html

I do know there is no native support for doing dotted stroke lines rendered on a canvas, but I have seen the clever ways people have been able to generate support for this.
What I am wondering is if there is any way to translate this to allow for rendering dotted strokes around shapes (specifically circles)?

the simplest way using context.setLineDash()
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.arc(100, 60, 50, 0, Math.PI * 2);
ctx.closePath();
ctx.stroke();

Live Demo
calcPointsCirc takes 4 arguments, the center x/y, the radius, and the length of the dashes. It returns an array of points, x,y,ex,ey. You can just loop through the points to draw the dashed circle. There's probably much more elegant ways to do this but figured Id give it a shot.
function calcPointsCirc( cx,cy, rad, dashLength)
{
var n = rad/dashLength,
alpha = Math.PI * 2 / n,
pointObj = {},
points = [],
i = -1;
while( i < n )
{
var theta = alpha * i,
theta2 = alpha * (i+1);
points.push({x : (Math.cos(theta) * rad) + cx, y : (Math.sin(theta) * rad) + cy, ex : (Math.cos(theta2) * rad) + cx, ey : (Math.sin(theta2) * rad) + cy});
i+=2;
}
return points;
}
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
canvas.width = canvas.height= 200;
var pointArray= calcPointsCirc(50,50,50, 1);
ctx.strokeStyle = "rgb(255,0,0)";
ctx.beginPath();
for(p = 0; p < pointArray.length; p++){
ctx.moveTo(pointArray[p].x, pointArray[p].y);
ctx.lineTo(pointArray[p].ex, pointArray[p].ey);
ctx.stroke();
}
ctx.closePath();

If all else fails you can always loop a variable from 0 to 2*pi, skipping every step items and drawing on every other step items points at sin(angle)*radius+centerx, cos(angle)*radius+centery.
There you go, home-made dotted circle :)

My JavaScript Path library implements dashed and dotted drawing of arbitrary paths (which can be composed of any number of straight or curved segments), including ellipses. Download it and check out the examples.

I was looking for a dashed-circle for my game and after reading all of the pages I have written a class in typescript it works very well. If anybody looks for the dashed-circle in typescript, it is here;
export class DashedCircle
{
centerX: number;
centerY: number;
radius: number;
color: string;
dashSize: number;
ctx: CanvasRenderingContext2D;
constructor(ctx:CanvasRenderingContext2D, centerX: number, centerY: number, radius: number, color: string, dashSize: number)
{
this.ctx = ctx;
this.centerX = centerX;
this.centerY = centerY;
this.radius = radius;
this.color = color;
this.dashSize = dashSize;
}
CalculateCirclePoints()
{
var n = this.radius / this.dashSize;
var alpha = Math.PI * 2 / n;
var pointObj = {};
var points = [];
var i = -1;
while (i < n)
{
var theta = alpha * i;
var theta2 = alpha * (i + 1);
points.push({
x: (Math.cos(theta) * this.radius) + this.centerX,
y: (Math.sin(theta) * this.radius) + this.centerY,
ex: (Math.cos(theta2) * this.radius) + this.centerX,
ey: (Math.sin(theta2) * this.radius) + this.centerY });
i += 2;
}
return points;
}
Draw()
{
var points = this.CalculateCirclePoints();
this.ctx.strokeStyle = this.color;
this.ctx.beginPath();
for (var p = 0; p < points.length; p++)
{
this.ctx.moveTo(points[p].x, points[p].y);
this.ctx.lineTo(points[p].ex, points[p].ey);
this.ctx.stroke();
}
this.ctx.closePath();
}
}

Related

Clip + Arc leads to an unwanted closing of the path, while Clip + Rect shows the expected behavior

Question:
Why does CanvasRenderingContext2D.clip() closes an additional path when applying it to a collection of CanvasRenderingContext2D.arc() sampled along the path of a quadratic curve?
Background
I am trying to create a path of quadratic segments with a longitudinal color split. Based on a comment to the question "Square curve with lengthwise color division" I am trying to accomplish this goal by going through the following steps:
Draw the quadratic path
Sample point on the quadratic curve
Create a clipping region and draw a cycle at each sampled point
let region = new Path2D();
for (j = 0; j < pointsQBez.length; j++) {
region.arc(pointsQBez[j].x, pointsQBez[j].y, 4, 0, 2 * Math.PI );
}
ctx.clip(region)
Split the canvas into two segments based on the curve
Calculate the intersection of the start- and end-segment with the canvas border
Close the path (first clipping region)
Draw a rectangle over the whole canvas (second clipping region)
Fill in the two regions created in step four
Steps 3, 4, and 5 in pictures:
Issue
The pink part in the third image above should have the same thickness as the turquoise.
But for some strange reason, the whole inner part of the curve gets filled in.
Additional observations
This behaviour does not show when using CanvasRenderingContext2D.rect() instead of CanvasRenderingContext2D.arc():
When using CanvasRenderingContext2D.arc(), the inner part of the curve that is filled in is not consistent
Because rect does include a call to closePath() while arc doesn't.
Two ways of working around that:
You can call closePath() after each arc:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const pointsQBez = [];
const cx = 75;
const cy = 75;
const rad = 50;
for(let i = 0; i < 180; i++) {
const a = (Math.PI / 180) * i - Math.PI / 2;
const x = cx + Math.cos(a) * rad;
const y = cy + Math.sin(a) * rad;
pointsQBez.push({ x, y });
}
let region = new Path2D();
for (const {x, y} of pointsQBez) {
region.arc(x, y, 4, 0, 2 * Math.PI);
region.closePath();
}
ctx.clip(region);
ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
<canvas></canvas>
Or you can moveTo() the entry point of your arc:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const pointsQBez = [];
const cx = 75;
const cy = 75;
const rad = 50;
for(let i = 0; i < 180; i++) {
const a = (Math.PI / 180) * i - Math.PI / 2;
const x = cx + Math.cos(a) * rad;
const y = cy + Math.sin(a) * rad;
pointsQBez.push({ x, y });
}
let region = new Path2D();
for (const {x, y} of pointsQBez) {
region.moveTo(x + 4, y); // x + arc radius
region.arc(x, y, 4, 0, 2 * Math.PI);
}
ctx.clip(region);
ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
<canvas></canvas>

In p5.js how do I create a moving animation of an array group of lines?

Hi I created a sparkler like shape in P5.js using array() and randomGaussian(). Here is what it looks like with the codes in p5.js:
let distribution = new Array(360);
let b = false;
let x, y;
function setup() {
var cny = createCanvas(windowWidth, windowHeight);
cnv.parent("sketchholder");
for (let i = 0; i < distribution.length; i++) {
distribution[i] = floor(randomGaussian(60, 50));
}
colorMode(HSB, 255);
// hue, saturation, brightness
x = width / 4;
у = height / 3;
}
function draw() {
background(21, 30, 10);
translate(width / 2, height / 2);
strokeWeight(3);
stroke(255, 70);
line(0, 0, -x, y);
if (b) {
for (let i = 0; i < distribution.length; i++) {
rotate(TWO_PI / distribution.length);
var colorH = random(0, 255);
var colorS = random(100, 200);
var colorB = random(0, 255);
var YY = random(1, 4);
stroke(colorH, colorS, colorB);
strokeWeight(YY);
strokeCap(ROUND);
let dist = abs(distribution[i]);
line(0, 0, dist, 0);
}
}
}
And I would like it to move slowly along the stick and disappear after like 20s before reaching the end. Can I achieve that in p5.js? Or can I create the same effect using the p5.js layer as a div in html and animate it in CSS?
Thanks.
Yes, you can definitely do this with p5.js. I think it would not make much sense to try to do an animation like this with CSS and that wouldn't work well with p5.js regardless (you would only be able to animate DOM elements). However, you do need to basically implement your own animation system with different parameter values for different times in the animation and then interpolating between them yourself. Here is a basic example:
let distribution = new Array(360);
let originX, originY;
let keyframes = [];
let keyframeTimes = [];
function setup() {
createCanvas(windowWidth, windowHeight);
for (let i = 0; i < distribution.length; i++) {
distribution[i] = floor(randomGaussian(60, 50));
}
colorMode(HSB, 255);
// hue, saturation, brightness
// Origin relative to the center of the sketch
originX = -width / 4;
originY = height / 3;
const lenX = width / 4;
const lenY = - height / 3;
// Add the desired parameters and times to keyframes and keyframeTimes
keyframes.push({
x: lenX,
y: lenY,
mag: 1
});
keyframeTimes.push(0);
keyframes.push({
x: 0.2 * lenX,
y: 0.2 * lenY,
mag: 0
});
keyframeTimes.push(20);
}
function draw() {
background(21, 30, 10);
translate(width / 2, height / 2);
translate(originX, originY);
strokeWeight(3);
stroke(255, 70);
// Find the next keyframe that we are animating towards.
let nextKeyframeIx = keyframeTimes.findIndex(t => t > millis() / 1000);
// Find the current/previous keyframe that we are starting from.
// Cases to consider: the next keyframe is the first one in the list, we're in
// between two keyframes, or there is no next keyframe
let keyframeIx =
nextKeyframeIx === 0 ?
// There is no previous keyframe (we're at the beginning)
0 :
// If we're beyond the end of the list, just use the parameters from the
// last keyframe
(nextKeyframeIx > 0 ? nextKeyframeIx - 1 : keyframes.length - 1);
let x, y, mag;
if (keyframeIx < nextKeyframeIx) {
// lerp between the current and next keyframe
let kf1 = keyframes[keyframeIx];
let kf2 = keyframes[nextKeyframeIx];
let t1 = keyframeTimes[keyframeIx];
let t2 = keyframeTimes[nextKeyframeIx];
let amt = (millis() / 1000 - t1) / (t2 - t1);
x = lerp(kf1.x, kf2.x, amt);
y = lerp(kf1.y, kf2.y, amt);
mag = lerp(kf1.mag, kf2.mag, amt);
} else {
// just use the current/previous keyframe
let kf = keyframes[keyframeIx];
x = kf.x;
y = kf.y;
mag = kf.mag;
}
// Draw a line from the origin (bottom left of the sparkler) to the current
// x,y position of the end of the sparkler
line(0, 0, x, y);
// Translate to the current end of the sparkler for drawing the colored lines
translate(x, y);
if (mag > 0) {
for (let i = 0; i < distribution.length; i++) {
rotate(TWO_PI / distribution.length);
const colorH = random(0, 255);
const colorS = random(100, 200);
const colorB = random(0, 255);
const YY = random(1, 4);
stroke(colorH, colorS, colorB);
strokeWeight(YY);
strokeCap(ROUND);
let dist = abs(distribution[i]) * mag;
line(0, 0, dist, 0);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

find coordinates to draw square inside circle

How to compute the starting co-ordinates to draw a square inside a cirle?
Function Draws the circular spectrum .
Now help me to find the starting coordinates to draw the rectangle inside the circle
Gradient.prototype.renderSpectrum = function() {
var radius = this.width / 2;
var toRad = (2 * Math.PI) / 360;
var step = 1 / radius;
this.ctx.clearRect(0, 0, this.width, this.height);
for(var i = 0; i < 360; i += step) {
var rad = i * toRad;
this.ctx.strokeStyle = 'hsl(' + i + ', 100%, 50%)';
this.ctx.beginPath();
this.ctx.moveTo(radius, radius);
this.ctx.lineTo(radius + radius * Math.cos(rad), radius + radius * Math.sin(rad));
this.ctx.stroke();
}
this.ctx.fillStyle = 'rgb(255, 255, 255)';
this.ctx.beginPath();
this.ctx.arc(radius, radius, radius * 0.8, 0, Math.PI * 2, true);
this.ctx.closePath();
return this.ctx.fill();
}
Function to draw the square
Gradient.prototype.renderGradient = function() {
var color, colors, gradient, index, xy, _i, _len, _ref, _ref1;
xy = arguments[0], colors = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
gradient = (_ref = this.ctx).createLinearGradient.apply(_ref, [0, 0].concat(__slice.call(xy)));
gradient.addColorStop(0, (_ref1 = colors.shift()) != null ? _ref1.toString() : void 0);
for (index = _i = 0, _len = colors.length; _i < _len; index = ++_i) {
color = colors[index];
gradient.addColorStop(index + 1 / colors.length, color.toString());
}
this.ctx.fillStyle = gradient;
this.renderSpectrum();
return this.ctx.fillRect(?, ?, this.width * 0.8, this.height * 0.8);
};
To fit a square inside a circle you can use something like this (adopt as needed):
Live example
/**
* ctx - context
* cx/cy - center of circle
* radius - radius of circle
*/
function squareInCircle(ctx, cx, cy, radius) {
var side = Math.sqrt(radius * radius * 2), // calc side length of square
half = side * 0.5; // position offset
ctx.strokeRect(cx - half, cy - half, side, side);
}
Just replace strokeRect() with fillRect().
Which will result in this (circle added for reference):
Adopting it for general usage:
function getSquareInCircle(cx, cy, radius) {
var side = Math.sqrt(radius * radius * 2), // calc side length of square
half = side * 0.5; // position offset
return {
x: cx - half,
y: cy - half,
w: side,
h: side
}
}
Then in your method:
Gradient.prototype.renderGradient = function() {
var color, colors, gradient, index, xy, _i, _len, _ref, _ref1;
xy = arguments[0], colors = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
gradient = (_ref = this.ctx).createLinearGradient.apply(_ref, [0, 0].concat(__slice.call(xy)));
gradient.addColorStop(0, (_ref1 = colors.shift()) != null ? _ref1.toString() : void 0);
for (index = _i = 0, _len = colors.length; _i < _len; index = ++_i) {
color = colors[index];
gradient.addColorStop(index + 1 / colors.length, color.toString());
}
this.ctx.fillStyle = gradient;
this.renderSpectrum();
// supply the proper position/radius here:
var square = getSquareInCircle(centerX, centerY, radius);
return this.ctx.fillRect(square.x, square.y, square.w, square.h);
};
Here is how I computed the co ordinates to draw square inside a circle
1)Get the coordinates of a inner circle at 135 degree
using the formula
x = rad + rad * Math.cos(135 * ( 2 Math.PI / 360);
y = rad - rad * Math.sin(135 * ( 2 Math.PI / 360);
2) then pyhthogoram therom to find the width if the square
width = Math.sqrt(rad * rad / 2);

How to move canvas speedometer needle slowly?

I use following codes in order to move a picture in canvas for my speedometer.
var meter = new Image,
needle = new Image;
window.onload = function () {
var c = document.getElementsByTagName('canvas')[0];
var ctx = c.getContext('2d');
setInterval(function () {
ctx.save();
ctx.clearRect(0, 0, c.width, c.height);
ctx.translate(c.width / 2, c.height / 2);
ctx.drawImage(meter, -165, -160);
ctx.rotate((x * Math.PI / 180);
/ x degree
ctx.drawImage( needle, -13, -121.5 );
ctx.restore();
},50);
};
meter.src = 'meter.png';
needle.src = 'needle.png';
}
However I want to move the needle slowly to the degree which I entered such as speedtest webpages. Any idea?
Thanks.
Something like this should work:
var meter = new Image,
needle = new Image;
window.onload = function () {
var c = document.getElementsByTagName('canvas')[0],
ctx = c.getContext('2d'),
x, // Current angle
xTarget, // Target angle.
step = 1; // Angle change step size.
setInterval(function () {
if(Math.abs(xTarget - x) < step){
x = xTarget; // If x is closer to xTarget than the step size, set x to xTarget.
}else{
x += (xTarget > x) ? step : // Increment x to approach the target.
(xTarget < x) ? -step : // (Or substract 1)
0;
}
ctx.save();
ctx.clearRect(0, 0, c.width, c.height);
ctx.translate(c.width / 2, c.height / 2);
ctx.drawImage(meter, -165, -160);
ctx.rotate((x * Math.PI / 180); // x degree
ctx.drawImage( needle, -13, -121.5 );
ctx.restore();
},50);
};
dial.src = 'meter.png';
needle.src = 'needle.png';
}
I'm using a shorthand if / else here to determine whether to add 1 to x, substract 1, or do nothing. Functionally, this is the same as:
if(xTarget > x){
x += step;
}else if(xTarget < x){
x += -step;
}else{
x += 0;
}
But it's shorter, and in my opinion, just as easy to read, once you know what a shorthand if (ternary operator) looks like.
Please keep in mind that this code assumes x is a integer value (So, not a float, just a rounded int).

How to produce such page flip effects in Canvas?

google has published an e-book : http://www.20thingsilearned.com/
the reading experience is kind of fun.
I notice they used canvas to produce the page flip effects over reading areas.
Technically, you can draw a customized shape, filled with gradient, decorated with shadow.
but the shape has to be drawn with two beizer curves (for top and bottom edges) and two straight lines.
The problem is how to dynamically draw those two beizer curves. I spent a whole day to draw these two curves. here is some code of.
Does anyone knows how to produce the same effects on Google's ebook.
or i basically ran into wrong direction?
/**
* PageFlip effects using HTML Canvas
* #author RobinQu
* #version 0.1
*/
/*global x$ */
var elf = {};
elf.fx = {};
elf.fx.pageflip = {
/**
* initialize the pageflip
* #param {String} cId The id of canvas element
*/
init: function(cId, width, height) {
var canvas,
ctx;
this.$ = x$("#" + cId);
canvas = this.canvas = this.$[0];
this.width = canvas.width = width;
this.height = canvas.height = height;
this.margin = 60;
this.context = canvas.getContext("2d");
//this.bind();
},
bind: function() {
this.$.on("mouseover", this.beginRoll.bind(this));
this.$.on("mousemove", this.doRoll.bind(this));
this.$.on("mouseout", this.endRoll.bind(this));
},
_over: false,
beginRoll: function() {
this._over = true;
},
_clear: function() {
var ctx = this.context;
ctx.clearRect(0, 0, this.width, this.height);
var w = this.width;
this.canvas.width = 1;
this.canvas.width = w;
},
doRoll: function(e) {
//offset, plus scroll; client, no scroll
if (this._over) {
//console.log(e.offsetX, e.offsetY, e.clientX, e.clientY);
var x = e.offsetX,
y = e.offsetY,
ctx = this.context,
startx = x,
starty = x / this.width * this.margin,
endx = (this.width - x)/2 + x,
endy = this.margin + 8,
cp1x = x + 10,
cp1y = Math.min(this.margin * Math.sin(x * Math.PI / this.width), 5),
cp2x = endx - 10,
cp2y = Math.min(this.margin * Math.cos(x * Math.PI / this.width), 5);
console.log(this.margin * Math.sin(x * Math.PI / this.width));
//enxy = this.margin * Math.sin(x * Math.PI * 2 / this.width),
//cp2x = ;
console.log(this.width, this.height);
this._clear();
ctx.beginPath();
ctx.moveTo(startx, starty);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endx, endy);
ctx.strokeStyle = "#000";
ctx.stroke();
}
},
endRoll: function() {
this._over = false;
}
};