Canvas fill area and circle in a chart - javascript

I am trying to draw a line chart with different colors for different parts of the charts.
I am having trouble figuring out how to draw individual fillStyle and strokeStyle
Attached code with comments
const canvas = document.getElementById('test');
const ctx = canvas.getContext('2d');
const width = canvas.width = 1000;
const height = canvas.height = 500;
ctx.fillStyle = 'blue';
function plotPoints() {
const pts = generatePoints(25);
pts.forEach((pt, index, pointArray) => {
drawCurvedLine(ctx, pt, index, pointArray)
});
ctx.stroke();
const maxY = Math.max.apply(null, pts.map(pt => pt.y));
ctx.lineTo(pts[pts.length - 1].x, maxY);
ctx.lineTo(pts[0].x, maxY);
// Area Color
ctx.fillStyle = 'rgba(255, 148, 136, .6)';
ctx.fill();
}
plotPoints();
function generatePoints(nbOfPoints) {
const pts = [];
for (let i = 0; i <= nbOfPoints; i++) {
pts.push({
x: i * (width / nbOfPoints),
y: Math.random() * height
});
}
return pts;
}
function drawCurvedLine(ctx, point, index, pointArray) {
if (typeof pointArray[index + 1] !== 'undefined') {
var x_mid = (point.x + pointArray[index + 1].x) / 2;
var y_mid = (point.y + pointArray[index + 1].y) / 2;
var cp_x1 = (x_mid + point.x) / 2;
var cp_x2 = (x_mid + pointArray[index + 1].x) / 2;
// Point fill color crimson
// Point stroke style blue for example
ctx.fillStyle = 'crimson';
ctx.strokeStyle = 'blue';
ctx.arc(point.x, point.y, 10, 2 * Math.PI, false);
// ctx.stroke();
// ctx.fill();
ctx.quadraticCurveTo(cp_x1, point.y, x_mid, y_mid);
ctx.quadraticCurveTo(cp_x2, pointArray[index + 1].y, pointArray[index + 1].x, pointArray[index + 1].y);
// Line stroke style salmon
ctx.strokeStyle = 'salmon';
ctx.lineWidth = 5;
}
}
<canvas id="test"></canvas>
Any help is much appreciated.

You need to use the ctx.beginPath(); when you are drawing to a canvas...
Here is your snippet with a different approach:
const canvas = document.getElementById('test');
const ctx = canvas.getContext('2d');
const width = canvas.width = 600;
const height = canvas.height = 250;
plotPoints();
function plotPoints() {
const pts = generatePoints(50);
ctx.strokeStyle = 'salmon';
ctx.fillStyle = 'rgba(255, 148, 136, .6)';
ctx.lineWidth = 5;
ctx.beginPath();
pts.forEach((pt, index, pointArray) => {
drawCurvedLine(pt, pointArray[index + 1])
});
ctx.fill();
ctx.stroke();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
for (i = 5; i < pts.length-5; i++) {
ctx.beginPath();
ctx.fillStyle = 'rgba(0,'+ pts[i].y +','+ pts[i].x/2 +')';
ctx.arc(pts[i].x, pts[i].y, 5, 2 * Math.PI, false)
ctx.stroke();
ctx.fill();
}
}
function generatePoints(nbOfPoints) {
const pts = [{x:0, y: height}];
for (let i = 0; i <= nbOfPoints; i++) {
pts.push({x: i * (width / nbOfPoints), y: Math.sin(i/2.6) * height/3 + 100});
}
pts.push({x:width, y: height});
return pts;
}
function drawCurvedLine(point, next) {
if (typeof next !== 'undefined') {
var x_mid = (point.x + next.x) / 2;
var y_mid = (point.y + next.y) / 2;
var cp_x1 = (x_mid + point.x) / 2;
var cp_x2 = (x_mid + next.x) / 2;
ctx.quadraticCurveTo(cp_x1, point.y, x_mid, y_mid);
ctx.quadraticCurveTo(cp_x2, next.y, next.x, next.y);
}
}
<canvas id="test"></canvas>

Related

how to move 2 object, one on the X axis and the other on the Y axis in Javascript?

I am new to javascript, and I need to animate 2 squares of the same size using javascript, one moves on the Y-axis and another on the X-axis. when I run the code, they both go diagonally, what should I change?
I've tried to:
create a different function of draw() for the blue rectangle, which resulted in the same problem
make a drawBlueRectangle() function which draws the blue rectangle. nothing changed.
the two rectangles' code works fine separately
var ctx = canvasObject.getContext("2d");
var squareX = 200;
var squareY = 150;
var diffY = 5;
var diffX = 5;
var squareSize = 10;
var WIDTH = 400;
var HEIGHT = 300;
function drawRect() {
ctx.fillRect(0, 0, WIDTH, HEIGHT);
}
function drawSquare() {
ctx.beginPath();
ctx.fillRect(squareX, squareY, squareSize, squareSize);
}
function draw() {
ctx.fillStyle = "white";
drawRect();
ctx.fillStyle = "red";
drawSquare();
if ((squareY + squareSize) >= HEIGHT)
diffY = -diffY;
if (squareY <= 0)
diffY = -diffY;
squareY = squareY + diffY;
ctx.fillStyle = "blue";
drawSquare();
if ((squareX + squareSize) == WIDTH)
diffX = -diffX;
if (squareX <= 0)
diffX = -diffX;
squareX = squareX + diffX;
}
setInterval(draw, 30);
Just a small change, maintain separate x and y coordinate for both the rectangles so that the y-coordinate remain constant(to move on the x-axis) and the same for the other one.
Here's the updated code
var ctx = canvasObject.getContext("2d");
var squareX = 100;
var squareY = 50;
var squareX1 = 100;
var squareY1 = 50;
var diffY = 5;
var diffX = 5;
var squareSize = 10;
var WIDTH = 400;
var HEIGHT = 300;
function drawRect() {
ctx.fillRect(0, 0, WIDTH, HEIGHT);
}
function drawSquare() {
ctx.beginPath();
ctx.fillRect(squareX, squareY, squareSize, squareSize);
}
function drawSquare1() {
ctx.beginPath();
ctx.fillRect(squareX1, squareY1, squareSize, squareSize);
}
function draw() {
ctx.fillStyle = "black";
drawRect();
ctx.fillStyle = "red";
drawSquare();
if ((squareY+squareSize) > HEIGHT-200)
diffY = -diffY;
if (squareY <= 0)
diffY = -diffY;
squareY = squareY + diffY;
ctx.fillStyle = "blue";
drawSquare1();
if ((squareX1 + squareSize) > WIDTH-200)
diffX = -diffX;
if (squareX1 < 0)
diffX = -diffX;
squareX1 = squareX1 + diffX;
}
setInterval(draw, 30);
If you pass in the coordinates to draw the rectangle into the drawSquare function, then the same code can be used to draw squares in different locations.
const canvasObject = document.getElementById("cvs");
const ctx = canvasObject.getContext("2d");
const squareSize = 10;
const WIDTH = 400;
const HEIGHT = 300;
let squareX = 200;
let squareY = 150;
let diffY = 5;
let diffX = 5;
function drawSquare(x, y) {
ctx.fillRect(x, y, squareSize, squareSize);
}
function draw() {
// Draw background
ctx.fillStyle = "white";
ctx.fillRect(0, 0, WIDTH, HEIGHT);
// Move squares and handle edge collisions
squareY = squareY + diffY;
squareX = squareX + diffX;
if (((squareY + squareSize) >= HEIGHT) || (squareY <= 0)) { diffY = -diffY; }
if (((squareX + squareSize) >= WIDTH) || (squareX <= 0)) { diffX = -diffX; }
ctx.fillStyle = "red";
drawSquare(WIDTH / 2, squareY);
ctx.fillStyle = "blue";
drawSquare(squareX, HEIGHT / 2);
}
setInterval(draw, 30);
<canvas id="cvs" width="400" height="300"></canvas>

How to draw polygon when last point clicked?

I want to complete polygon when starting point of polygon clicked, i think i need to hide or set opacity of last line which is linking to first point of line.
goal is to achieve exactly what is happening in this GIF.
also i want to show angles and length on each line while drawing just like exactly same as GIF.
var bw = window.innerWidth -20;
var bh = window.innerHeight -20;
var p = 10;
var cw = bw + (p*2) + 1;
var ch = bh + (p*2) + 1;
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var context = canvas.getContext("2d");
function drawBoard(){
for (var x = 0; x <= bw; x += 30) {
context.moveTo(0.5 + x + p, p);
context.lineTo(0.5 + x + p, bh + p);
}
for (var x = 0; x <= bh; x += 30) {
context.moveTo(p, 0.5 + x + p);
context.lineTo(bw + p, 0.5 + x + p);
}
context.lineWidth = 0.5;
context.strokeStyle = 'rgba(0, 122, 204,0.7)';
context.stroke();
}
drawBoard();
//---------------------------------------------
requestAnimationFrame(update)
mouse = {x : 0, y : 0, button : 0, lx : 0, ly : 0, update : true};
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.update = true;
}
["mousedown","mouseup","mousemove"].forEach(name => document.addEventListener(name,mouseEvents));
context.lineWidth = 2;
context.strokeStyle = "red";
const point = (x,y) => ({x,y});
const poly = () => ({
points : [],
addPoint(p){ this.points.push(point(p.x,p.y)) },
draw() {
context.lineWidth = 2;
context.strokeStyle = "red";
context.fillStyle = 'rgba(255, 0, 0, 0.3)';
context.beginPath();
for (const p of this.points) { context.lineTo(p.x,p.y) }
context.closePath();
context.stroke();
context.fill();
context.fillStyle = 'rgba(0, 0, 0, 0.3)';
context.strokeStyle = 'rgba(0, 0, 0, 0.8)';
for (const p of this.points) {
context.beginPath();
context.moveTo(p.x + 10,p.y);
context.arc(p.x,p.y,10,0,Math.PI *2);
context.fill();
context.stroke();
}
},
closest(pos, dist = 8) {
var i = 0, index = -1;
dist *= dist;
for (const p of this.points) {
var x = pos.x - p.x;
var y = pos.y - p.y;
var d2 = x * x + y * y;
if (d2 < dist) {
dist = d2;
index = i;
}
i++;
}
if (index > -1) { return this.points[index] }
}
});
function drawCircle(pos,color="black",size=8){
context.strokeStyle = color;
context.beginPath();
context.arc(pos.x,pos.y,size,0,Math.PI *2);
context.stroke();
}
const polygon = poly();
var activePoint,cursor;
var dragging= false;
function update(){
if (mouse.update) {
cursor = "crosshair";
context.clearRect(0,0,canvas.width,canvas.height);
drawBoard();
if (!dragging) { activePoint = polygon.closest(mouse) }
if (activePoint === undefined && mouse.button) {
polygon.addPoint(mouse);
mouse.button = false;
} else if(activePoint) {
if (mouse.button) {
if(dragging) {
activePoint.x += mouse.x - mouse.lx;
activePoint.y += mouse.y - mouse.ly;
} else { dragging = true }
} else { dragging = false }
}
polygon.draw();
if (activePoint) {
drawCircle(activePoint);
cursor = "move";
}
mouse.lx = mouse.x;
mouse.ly = mouse.y;
canvas.style.cursor = cursor;
mouse.update = false;
}
requestAnimationFrame(update)
}
<html>
<body style=" background: lightblue;">
<canvas id="canvas" style="background: #fff; magrin:20px;"></canvas>
</body>
</html>
Drawing part of code i copied from: https://stackoverflow.com/a/53437943/3877726
You can use ctx.setTransform to to align text to a line.
First normalize the vector between the end points and use that normalized vector to build the transform. See example.
To prevent text from reading back to front you need to check the x component of the normalized vector. If it is < 0 then reverse the vector.
Almost the same for the angle. See example.
Example
Snippets contain the functions drawLineText and drawAngleText (near top) that implement the additional features.
var bw = innerWidth - 20, bh = innerHeight - 20;
var cw = bw + (p * 2) + 1, ch = bh + (p * 2) + 1;
var p = 10;
var activePoint, cursor, dragging = false;
const mouse = {x: 0, y: 0, button: 0, lx: 0, ly: 0, update: true};
const TEXT_OFFSET = 5;
const TEXT_COLOR = "#000";
const TEXT_SIZE = 16;
const FONT = "arial";
const TEXT_ANGLE_OFFSET = 25;
const DEG = "°";
canvas.width = bw;
canvas.height = bh;
var ctx = canvas.getContext("2d");
function drawLineText(p1, p2, text, textOffset = TEXT_OFFSET, textColor = TEXT_COLOR, textSize = TEXT_SIZE, font = FONT) {
var x = p1.x, y = p1.y;
var nx = p2.x - x, ny = p2.y - y, len = (nx * nx + ny * ny) ** 0.5;
nx /= len;
ny /= len;
ctx.font = textSize + "px " + font;
ctx.textAlign = "center";
ctx.fillStyle = textColor;
if (nx < 0) {
ctx.textBaseline = "top";
x = p2.x;
y = p2.y;
nx = -nx;
ny = -ny;
textOffset = -textOffset;
} else { ctx.textBaseline = "bottom" }
len /= 2;
ctx.setTransform(nx, ny, -ny, nx, x, y);
ctx.fillText(text, len, -textOffset);
}
// angle between p2-p1 and p2-p3
function drawAngleText(p1, p2, p3, textAngleOffset = TEXT_ANGLE_OFFSET, textColor = TEXT_COLOR, textSize = TEXT_SIZE, font = FONT) {
var ang;
var x = p2.x, y = p2.y;
var nx1 = p1.x - x, ny1 = p1.y - y, len1 = (nx1 * nx1 + ny1 * ny1) ** 0.5;
var nx2 = p3.x - x, ny2 = p3.y - y, len2 = (nx2 * nx2 + ny2 * ny2) ** 0.5;
nx1 /= len1;
ny1 /= len1;
nx2 /= len2;
ny2 /= len2;
const cross = nx1 * ny2 - ny1 * nx2;
const dot = nx1 * nx2 + ny1 * ny2;
if (dot < 0) {
ang = cross < 0 ? -Math.PI - Math.asin(cross) : Math.PI - Math.asin(cross);
} else {
ang = Math.asin(cross);
}
const angDeg = Math.abs(ang * (180 / Math.PI)).toFixed(0) + DEG;
ctx.font = textSize + "px " + font;
ctx.fillStyle = textColor;
ctx.textBaseline = "middle";
const centerAngle = Math.atan2(ny1, nx1) + ang / 2;
const nx = Math.cos(centerAngle);
const ny = Math.sin(centerAngle);
if (nx < 0) {
ctx.textAlign = "right";
ctx.setTransform(-nx, -ny, ny, -nx, x, y);
textAngleOffset = -textAngleOffset;
} else {
ctx.textAlign = "left";
ctx.setTransform(nx, ny, -ny, nx, x, y);
}
ctx.fillText(angDeg, textAngleOffset, 0);
}
//---------------------------------------------
requestAnimationFrame(update)
function mouseEvents(e) {
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.update = true;
}
["mousedown", "mouseup", "mousemove"].forEach(name => document.addEventListener(name, mouseEvents));
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
const point = (x, y) => ({x, y});
const poly = () => ({
points: [],
closed: false,
addPoint(p) { this.points.push(point(p.x, p.y)) },
draw() {
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
ctx.beginPath();
for (const p of this.points) { ctx.lineTo(p.x, p.y) }
this.closed && ctx.closePath();
ctx.stroke();
this.closed && ctx.fill();
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
for (const p of this.points) {
ctx.beginPath();
ctx.moveTo(p.x + 10, p.y);
ctx.arc(p.x, p.y, 10, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
this.points.length > 1 && this.drawLengthText();
this.points.length > 2 && this.drawAngleText();
},
drawLengthText() {
const len = this.points.length;
var p1, i = 0;
p1 = this.points[i];
while (i < len -(this.closed ? 0 : 1)) {
const p2 = this.points[((i++) + 1) % len];
const lineLength = ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5
drawLineText(p1, p2, lineLength.toFixed(0) + "px");
if (len < 3) { break }
p1 = p2;
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
},
drawAngleText() {
const len = this.points.length;
var p1, p2, i = this.closed ? 0 : 1;
p1 = this.points[(i + len - 1) % len];
p2 = this.points[i];
while (i < len -(this.closed ? 0 : 1)) {
const p3 = this.points[((i++) + 1) % len];
drawAngleText(p1, p2, p3);
p1 = p2;
p2 = p3;
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
},
closest(pos, dist = 8) {
var i = 0,
index = -1;
dist *= dist;
for (const p of this.points) {
var x = pos.x - p.x;
var y = pos.y - p.y;
var d2 = x * x + y * y;
if (d2 < dist) {
dist = d2;
index = i;
}
i++;
}
if (index > -1) { return this.points[index] }
}
});
const polygon = poly();
function drawCircle(pos, color = "black", size = 8) {
ctx.strokeStyle = color;
ctx.beginPath();
ctx.arc(pos.x, pos.y, size, 0, Math.PI * 2);
ctx.stroke();
}
function update() {
if (mouse.update) {
cursor = "crosshair";
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!dragging) { activePoint = polygon.closest(mouse) }
if (activePoint === undefined && mouse.button) {
polygon.addPoint(mouse);
mouse.button = false;
} else if (activePoint) {
if (mouse.button) {
if (dragging) {
activePoint.x += mouse.x - mouse.lx;
activePoint.y += mouse.y - mouse.ly;
} else {
if (!polygon.closed && polygon.points.length > 2 && activePoint === polygon.points[0]) {
polygon.closed = true;
}
dragging = true
}
} else { dragging = false }
}
polygon.draw();
if (activePoint) {
drawCircle(activePoint);
cursor = "move";
}
mouse.lx = mouse.x;
mouse.ly = mouse.y;
canvas.style.cursor = cursor;
mouse.update = false;
}
requestAnimationFrame(update)
}
Click to add points
<canvas id="canvas" style="background: #fff; magrin:20px;"></canvas>
Note though not mandatory it is customarily considered polite to include attributions when copying code.

How to draw parallel rectangles with rubber-band in canvas

I am trying to draw a group of shapes using canvas.
I have referenced below SO threads:
Draw a parallel line
How to draw parallel line using three.js?
but not able to figure out how to calculate points for the rectangles parallel in as we stretch the line.
Any reference for stretching shapes with canvas is appreciated.
//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var canvasx = $(canvas).offset().left;
var canvasy = $(canvas).offset().top;
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
// grid parameters
var gridSpacing = 20; // pixels
var gridWidth = 1;
//var gridColor = "#f1f1f1";
var gridColor = "lightgray";
/** */
var originX = 0;
/** */
var originY = 0;
drawGrid();
//Mousedown
$(canvas).on('mousedown', function(e) {
last_mousex = parseInt(e.clientX-canvasx);
last_mousey = parseInt(e.clientY-canvasy);
mousedown = true;
});
//Mouseup
$(canvas).on('mouseup', function(e) {
mousedown = false;
});
//Mousemove
$(canvas).on('mousemove', function(e) {
mousex = parseInt(e.clientX-canvasx);
mousey = parseInt(e.clientY-canvasy);
if(mousedown) {
ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
drawGrid();
ctx.setLineDash([5, 15]);
ctx.beginPath();
ctx.moveTo(last_mousex,last_mousey);
ctx.lineTo(mousex,mousey);
//ctx.lineTo(mousex,mousey);
ctx.strokeStyle = 'blue';
ctx.lineDashOffset = 2;
ctx.lineWidth = 5;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
startx = last_mousex;
starty = last_mousey;
drawPolygon([last_mousex, mousex, mousex, last_mousex, last_mousex],
[last_mousey-10, mousey-10, mousey-60, last_mousey-60],true, 'gray', false, 'black', 2);
drawPolygon([last_mousex, mousex, mousex, last_mousex, last_mousex],
[last_mousey+10, mousey+10, mousey+60, last_mousey+60],true, 'gray', false, 'black', 2);
}
//Output
$('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});
/** */
function drawLine(startX, startY, endX, endY, width, color) {
// width is an integer
// color is a hex string, i.e. #ff0000
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.stroke();
}
function drawPolygon(xArr, yArr, fill, fillColor, stroke, strokeColor, strokeWidth) {
// fillColor is a hex string, i.e. #ff0000
fill = fill || false;
stroke = stroke || false;
ctx.beginPath();
ctx.moveTo(xArr[0], yArr[0]);
for (var i = 1; i < xArr.length; i++) {
ctx.lineTo(xArr[i], yArr[i]);
}
ctx.closePath();
if (fill) {
ctx.fillStyle = fillColor;
ctx.fill();
}
if (stroke) {
ctx.lineWidth = strokeWidth;
ctx.strokeStyle = strokeColor;
ctx.stroke();
}
//console.log(xArr);
//console.log(yArr);
}
/** returns n where -gridSize/2 < n <= gridSize/2 */
function calculateGridOffset(n) {
if (n >= 0) {
return (n + gridSpacing / 2.0) % gridSpacing - gridSpacing / 2.0;
} else {
return (n - gridSpacing / 2.0) % gridSpacing + gridSpacing / 2.0;
}
}
/** */
function drawGrid() {
var offsetX = calculateGridOffset(-originX);
var offsetY = calculateGridOffset(-originY);
var width = canvas.width;
var height = canvas.height;
for (var x = 0; x <= (width / gridSpacing); x++) {
drawLine(gridSpacing * x + offsetX, 0, gridSpacing * x + offsetX, height, gridWidth, gridColor);
}
for (var y = 0; y <= (height / gridSpacing); y++) {
drawLine(0, gridSpacing * y + offsetY, width, gridSpacing * y + offsetY, gridWidth, gridColor);
}
}
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width="800" height="500"></canvas>
<div id="output"></div>
I'm guessing this is what you wanted.
Instead of trying to manually draw your line rotated, instead, move the origin of the canvas to the start of the line,
// save the canvas state
ctx.save();
// move origin to start of line
ctx.translate(last_mousex, last_mousey);
then rotate the origin so it points toward the end of the line in the positive X direction
// compute direction of line from start to end
const dx = mousex - last_mousex;
const dy = mousey - last_mousey;
const angle = Math.atan2(dy, dx);
// rotate to point to end of line
ctx.rotate(angle);
then compute the length of the line from the start to the end
// compute length of line
const length = Math.sqrt(dx * dx + dy * dy);
and just draw an arrow in the positive x direction of that length
ctx.setLineDash([5, 15]);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(length, 0);
ctx.strokeStyle = 'blue';
ctx.lineDashOffset = 2;
ctx.lineWidth = 5;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
drawPolygon([0, length, length, 0, 0],
[-10, -10, -60, -60],true, 'gray', false, 'black', 2);
drawPolygon([0, length, length, 0, 0],
[+10, +10, +60, +60],true, 'gray', false, 'black', 2);
// restore the canvas state
ctx.restore();
while we're at it your code for calculating the mouse position didn't work if the page is scrolled. This will get the mouse position relative to the pixels in the canvas.
const rect = canvas.getBoundingClientRect();
mousex = (e.clientX - rect.left) * canvas.width / canvas.clientWidth;
mousey = (e.clientY - rect.top ) * canvas.height / canvas.clientHeight;
//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
// grid parameters
var gridSpacing = 20; // pixels
var gridWidth = 1;
//var gridColor = "#f1f1f1";
var gridColor = "lightgray";
/** */
var originX = 0;
/** */
var originY = 0;
drawGrid();
//Mousedown
$(canvas).on('mousedown', function(e) {
const rect = canvas.getBoundingClientRect();
last_mousex = (e.clientX - rect.left) * canvas.width / canvas.clientWidth;
last_mousey = (e.clientY - rect.top ) * canvas.height / canvas.clientHeight;
mousedown = true;
});
//Mouseup
$(canvas).on('mouseup', function(e) {
mousedown = false;
});
//Mousemove
$(canvas).on('mousemove', function(e) {
const rect = canvas.getBoundingClientRect();
mousex = (e.clientX - rect.left) * canvas.width / canvas.clientWidth;
mousey = (e.clientY - rect.top ) * canvas.height / canvas.clientHeight;
if(mousedown) {
ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
drawGrid();
// save the canvas state
ctx.save();
// move origin to start of line
ctx.translate(last_mousex, last_mousey);
// compute direction of line from start to end
const dx = mousex - last_mousex;
const dy = mousey - last_mousey;
const angle = Math.atan2(dy, dx);
// rotate to point to end of line
ctx.rotate(angle);
// compute length of line
const length = Math.sqrt(dx * dx + dy * dy);
ctx.setLineDash([5, 15]);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(length, 0);
ctx.strokeStyle = 'blue';
ctx.lineDashOffset = 2;
ctx.lineWidth = 5;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
drawPolygon([0, length, length, 0, 0],
[-10, -10, -60, -60],true, 'gray', false, 'black', 2);
drawPolygon([0, length, length, 0, 0],
[+10, +10, +60, +60],true, 'gray', false, 'black', 2);
// restore the canvas state
ctx.restore();
}
//Output
$('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});
/** */
function drawLine(startX, startY, endX, endY, width, color) {
// width is an integer
// color is a hex string, i.e. #ff0000
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.stroke();
}
function drawPolygon(xArr, yArr, fill, fillColor, stroke, strokeColor, strokeWidth) {
// fillColor is a hex string, i.e. #ff0000
fill = fill || false;
stroke = stroke || false;
ctx.beginPath();
ctx.moveTo(xArr[0], yArr[0]);
for (var i = 1; i < xArr.length; i++) {
ctx.lineTo(xArr[i], yArr[i]);
}
ctx.closePath();
if (fill) {
ctx.fillStyle = fillColor;
ctx.fill();
}
if (stroke) {
ctx.lineWidth = strokeWidth;
ctx.strokeStyle = strokeColor;
ctx.stroke();
}
//console.log(xArr);
//console.log(yArr);
}
/** returns n where -gridSize/2 < n <= gridSize/2 */
function calculateGridOffset(n) {
if (n >= 0) {
return (n + gridSpacing / 2.0) % gridSpacing - gridSpacing / 2.0;
} else {
return (n - gridSpacing / 2.0) % gridSpacing + gridSpacing / 2.0;
}
}
/** */
function drawGrid() {
var offsetX = calculateGridOffset(-originX);
var offsetY = calculateGridOffset(-originY);
var width = canvas.width;
var height = canvas.height;
for (var x = 0; x <= (width / gridSpacing); x++) {
drawLine(gridSpacing * x + offsetX, 0, gridSpacing * x + offsetX, height, gridWidth, gridColor);
}
for (var y = 0; y <= (height / gridSpacing); y++) {
drawLine(0, gridSpacing * y + offsetY, width, gridSpacing * y + offsetY, gridWidth, gridColor);
}
}
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width="800" height="500"></canvas>
<div id="output"></div>

switch color (context.fillStyle) when drawing circles with <canvas>

I found the codes on codepen and modified as follow:
<canvas id="heatmap" width=300 height=150></canvas>
var canvas;
var context;
var screenH;
var screenW;
var circles = [];
var numcircles = 30;
$('document').ready(function() {
canvas = $('#heatmap');
screenH = canvas.height();
screenW = canvas.width();
canvas.attr('height', screenH);
canvas.attr('width', screenW);
context = canvas[0].getContext('2d');
for(var i = 0; i < numcircles; i++) {
var x = Math.round( Math.random() * screenW);
var y = Math.round( Math.random() * screenH);
var opacity = Math.random();
var circle = new Circle(x, y, opacity);
circles.push(circle);
}
drawCircles();
});
function drawCircles() {
var i = 0, me = this;
if (!circles.length) return;
(function loop() {
var circle = circles[i++];
circle.draw(context);
if (i < circles.length)
setTimeout(loop, 16);
})();
}
function Circle(x, y, opacity) {
this.x = parseInt(x);
this.y = parseInt(y);
this.opacity = opacity;
}
Circle.prototype.draw = function(){
context.save();
context.translate(this.x, this.y);
context.beginPath()
context.arc(0,0,Math.floor(Math.random() * (40 - 10) / 10) * 10 + 10,0,2*Math.PI);
context.closePath();
context.fillStyle = "rgba(25, 35, 50, " + this.opacity + ")";
context.shadowBlur = 5;
context.shadowColor = '#ffffff';
context.fill();
context.restore();
}
So I have 30 cirlces now. I like to have last 5 circles as red color.(25 as blue, 5 red)
context.fillStyle = "rgba(190, 60, 80, " + this.opacity + ")";
What is the best practice to achieve the goal?
There is never a best way to do anything. When you are pushing the circles you can just send in the style there instead just the opacity.
var circle = new Circle(x, y, i + 6 > numcircles ? "rgba(190, 60, 80, " + opacity + ")" : "rgba(25, 35, 50, " + opacity + ")");
var canvas;
var context;
var screenH;
var screenW;
var circles = [];
var numcircles = 30;
$('document').ready(function() {
canvas = $('#heatmap');
screenH = canvas.height();
screenW = canvas.width();
canvas.attr('height', screenH);
canvas.attr('width', screenW);
context = canvas[0].getContext('2d');
for (var i = 0; i < numcircles; i++) {
var x = Math.round(Math.random() * screenW);
var y = Math.round(Math.random() * screenH);
var opacity = Math.random();
var circle = new Circle(x, y, i + 6 > numcircles ? "rgba(190, 60, 80, " + opacity + ")" : "rgba(25, 35, 50, " + opacity + ")");
circles.push(circle);
}
drawCircles();
});
function drawCircles() {
var i = 0,
me = this;
if (!circles.length) return;
(function loop() {
var circle = circles[i++];
circle.draw(context);
if (i < circles.length)
setTimeout(loop, 16);
})();
}
function Circle(x, y, style) {
this.x = parseInt(x);
this.y = parseInt(y);
this.style = style;
}
Circle.prototype.draw = function() {
context.save();
context.translate(this.x, this.y);
context.beginPath()
context.arc(0, 0, Math.floor(Math.random() * (40 - 10) / 10) * 10 + 10, 0, 2 * Math.PI);
context.closePath();
context.fillStyle = this.style;
context.shadowBlur = 5;
context.shadowColor = '#ffffff';
context.fill();
context.restore();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id=heatmap width=300 height=150></canvas>
Start by modifying the Circle method so it accepts a color.
Then use that color, if set, in the draw function.
Now you can do new Circle(x, y, opacity, color); where the last argument is an optional color
var canvas;
var context;
var screenH;
var screenW;
var circles = [];
var numcircles = 30;
$(document).ready(function() {
canvas = $('#heatmap');
screenH = canvas.height();
screenW = canvas.width();
canvas.attr('height', screenH);
canvas.attr('width', screenW);
context = canvas[0].getContext('2d');
for (var i = 0; i < numcircles; i++) {
var x = Math.round(Math.random() * screenW);
var y = Math.round(Math.random() * screenH);
var opacity = Math.random();
var color = i > 24 ? '255,0,0' : null; // CHANGE COLOR AFTER 24 (RGB for red)
var circle = new Circle(x, y, opacity, color);
circles.push(circle);
}
drawCircles();
});
function drawCircles() {
var i = 0,
me = this;
if (!circles.length) return;
(function loop() {
var circle = circles[i++];
circle.draw(context);
if (i < circles.length)
setTimeout(loop, 16);
})();
}
function Circle(x, y, opacity, color) { // add color argument
this.x = parseInt(x);
this.y = parseInt(y);
this.opacity = opacity;
this.color = color; // set color
}
Circle.prototype.draw = function(color) {
context.save();
context.translate(this.x, this.y);
context.beginPath()
context.arc(0, 0, Math.floor(Math.random() * (40 - 10) / 10) * 10 + 10, 0, 2 * Math.PI);
context.closePath();
/* use color, if set*/
context.fillStyle = 'rgba(' + (this.color || '25, 35, 50') + ", " + this.opacity + ")";
context.shadowBlur = 5;
context.shadowColor = '#ffffff';
context.fill();
context.restore();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="heatmap" width="600" height="400"></canvas>

filling circle with hexagons

I'm trying to find a way to put as much hexagons in a circle as possible. So far the best result I have obtained is by generating hexagons from the center outward in a circular shape.
But I think my calculation to get the maximum hexagon circles is wrong, especially the part where I use the Math.ceil() and Math.Floor functions to round down/up some values.
When using Math.ceil(), hexagons are sometimes overlapping the circle.
When using Math.floor() on the other hand , it sometimes leaves too much space between the last circle of hexagons and the circle's border.
var c_el = document.getElementById("myCanvas");
var ctx = c_el.getContext("2d");
var canvas_width = c_el.clientWidth;
var canvas_height = c_el.clientHeight;
var PI=Math.PI;
var PI2=PI*2;
var hexCircle = {
r: 110, /// radius
pos: {
x: (canvas_width / 2),
y: (canvas_height / 2)
}
};
var hexagon = {
r: 20,
pos:{
x: 0,
y: 0
},
space: 1
};
drawHexCircle( hexCircle, hexagon );
function drawHexCircle(hc, hex ) {
drawCircle(hc);
var hcr = Math.ceil( Math.sqrt(3) * (hc.r / 2) );
var hr = Math.ceil( ( Math.sqrt(3) * (hex.r / 2) ) ) + hexagon.space; // hexRadius
var circles = Math.ceil( ( hcr / hr ) / 2 );
drawHex( hc.pos.x , hc.pos.y, hex.r ); //center hex ///
for (var i = 1; i<=circles; i++) {
for (var j = 0; j<6; j++) {
var currentX = hc.pos.x+Math.cos(j*PI2/6)*hr*2*i;
var currentY = hc.pos.y+Math.sin(j*PI2/6)*hr*2*i;
drawHex( currentX,currentY, hex.r );
for (var k = 1; k<i; k++) {
var newX = currentX + Math.cos((j*PI2/6+PI2/3))*hr*2*k;
var newY = currentY + Math.sin((j*PI2/6+PI2/3))*hr*2*k;
drawHex( newX,newY, hex.r );
}
}
}
}
function drawHex(x, y, r){
ctx.beginPath();
ctx.moveTo(x,y-r);
for (var i = 0; i<=6; i++) {
ctx.lineTo(x+Math.cos((i*PI2/6-PI2/4))*r,y+Math.sin((i*PI2/6-PI2/4))*r);
}
ctx.closePath();
ctx.stroke();
}
function drawCircle( circle ){
ctx.beginPath();
ctx.arc(circle.pos.x, circle.pos.y, circle.r, 0, 2 * Math.PI);
ctx.closePath();
ctx.stroke();
}
<canvas id="myCanvas" width="350" height="350" style="border:1px solid #d3d3d3;">
If all the points on the hexagon are within the circle, the hexagon is within the circle. I don't think there's a simpler way than doing the distance calculation.
I'm not sure how to select the optimal fill point, (but here's a js snippet proving that the middle isn't always it). It's possible that when you say "hexagon circle" you mean hexagon made out of hexagons, in which case the snippet proves nothing :)
I made the hexagon sides 2/11ths the radius of the circle and spaced them by 5% the side length.
var hex = {x:0, y:0, r:10};
var circle = {x:100, y:100, r:100};
var spacing = 1.05;
var SQRT_3 = Math.sqrt(3);
var hexagon_offsets = [
{x: 1/2, y: -SQRT_3 / 2},
{x: 1, y: 0},
{x: 1/2, y: SQRT_3 / 2},
{x: -1/2, y: SQRT_3 / 2},
{x: -1, y: 0},
{x: -1/2, y: -SQRT_3 / 2}
];
var bs = document.body.style;
var ds = document.documentElement.style;
bs.height = bs.width = ds.height = ds.width = "100%";
bs.border = bs.margin = bs.padding = 0;
var c = document.createElement("canvas");
c.style.display = "block";
c.addEventListener("mousemove", follow, false);
document.body.appendChild(c);
var ctx = c.getContext("2d");
window.addEventListener("resize", redraw);
redraw();
function follow(e) {
hex.x = e.clientX;
hex.y = e.clientY;
redraw();
}
function drawCircle() {
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.r, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.stroke();
}
function is_in_circle(p) {
return Math.pow(p.x - circle.x, 2) + Math.pow(p.y - circle.y, 2) < Math.pow(circle.r, 2);
}
function drawLine(a, b) {
var within = is_in_circle(a) && is_in_circle(b);
ctx.strokeStyle = within ? "green": "red";
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.closePath();
ctx.stroke();
return within;
}
function drawShape(shape) {
var within = true;
for (var i = 0; i < shape.length; i++) {
within = drawLine(shape[i % shape.length], shape[(i + 1) % shape.length]) && within;
}
if (!within) return false;
ctx.fillStyle = "green";
ctx.beginPath();
ctx.moveTo(shape[0].x, shape[0].y);
for (var i = 1; i <= shape.length; i++) {
ctx.lineTo(shape[i % shape.length].x, shape[i % shape.length].y);
}
ctx.closePath();
ctx.fill();
return true;
}
function calculate_hexagon(x, y, r) {
return hexagon_offsets.map(function (offset) {
return {x: x + r * offset.x, y: y + r * offset.y};
})
}
function drawHexGrid() {
var hex_count = 0;
var grid_space = calculate_hexagon(0, 0, hex.r * spacing);
var y = hex.y;
var x = hex.x;
while (y > 0) {
y += grid_space[0].y * 3;
x += grid_space[0].x * 3;
}
while (y < c.height) {
x %= grid_space[1].x * 3;
while (x < c.width) {
var hexagon = calculate_hexagon(x, y, hex.r);
if (drawShape(hexagon)) hex_count++;
x += 3 * grid_space[1].x;
}
y += grid_space[3].y;
x += grid_space[3].x;
x += 2 * grid_space[1].x;
}
return hex_count;
}
function redraw() {
c.width = window.innerWidth;
c.height = window.innerHeight;
circle.x = c.width / 2;
circle.y = c.height / 2;
circle.r = Math.min(circle.x, circle.y) * 0.9;
hex.r = circle.r * (20 / 110);
ctx.clearRect(0, 0, c.width, c.height);
var hex_count = drawHexGrid();
drawCircle();
ctx.fillStyle = "rgb(0, 0, 50)";
ctx.font = "40px serif";
ctx.fillText(hex_count + " hexes within circle", 20, 40);
}

Categories