Is there any framework/engine that provide ability to draw 3d images on Canvas?
I am planning to draw some primitives (different shapes) located in one plane:
var dist = 2;
var hexHalfW = 35;
var lengthX = 20;
var hexR = Math.sqrt(lengthX*lengthX+hexHalfW*hexHalfW);//40.31128874
var hexDiag = 2*hexR;
var hexHeight = hexDiag;
var hexWidth = 2*hexHalfW;
function DrawHex(ctx, x, y)
{
var cx = x*(hexWidth + dist) - y*(hexWidth + dist)/2;
var cy = y*(hexR + lengthX + dist);
ctx.beginPath();
ctx.moveTo(cx, cy-hexR);
ctx.lineTo(cx+hexHalfW, cy-hexR+lengthX);
ctx.lineTo(cx+hexHalfW, cy+hexR-lengthX);
ctx.lineTo(cx, cy+hexR);
ctx.lineTo(cx-hexHalfW, cy+hexR-lengthX);
ctx.lineTo(cx-hexHalfW, cy-hexR+lengthX);
ctx.lineTo(cx, cy-hexR);
ctx.fill();
}
function draw()
{
var canvas = document.getElementById('tutorial');
if (canvas.getContext)
{
var ctx = canvas.getContext('2d');
//Pick Hexagon color, this one will be blue
ctx.fillStyle = "rgb(0, 0, 255)"; DrawHex(ctx, 1, 1);
ctx.fillStyle = "rgb(0, 0, 225)"; DrawHex(ctx, 3, 1);
ctx.fillStyle = "rgb(0, 0, 195)"; DrawHex(ctx, 4, 1);
ctx.fillStyle = "rgb(0, 0, 165)"; DrawHex(ctx, 6, 1);
ctx.fillStyle = "rgb(0, 255, 0)"; DrawHex(ctx, 3, 2);
ctx.fillStyle = "rgb(0, 225, 0)"; DrawHex(ctx, 4, 2);
ctx.fillStyle = "rgb(0, 195, 0)"; DrawHex(ctx, 5, 2);
}
}
I would like to draw these primitives in "perspective": closer shapes (in the bottom of the screen) will be bigger, shapes "far away" (in the top of the screen) needs to be smaller...
For this purpose shapes coordinates are needs to be recalculated.
Guess, I could find some formulas that allows to recalculate coordinates and write a proper functions... but, probably, there are some engines that already doing that?
Please advise.
Any thoughts are welcome!
Whatever framework you chose, you should learn to encapsulate your data into objects.
- View simple demo -
Hexagon
// Hexagon
function Hexagon(ctx, color, pos, size, scale) {
this.color = color;
this.ctx = ctx;
this.x = pos[0];
this.y = pos[1];
this.z = pos[2] || 0; // scale
this.width = size.width;
this.height = size.height;
}
Hexagon.draw
// Hexagon.draw
Hexagon.prototype.draw = function (x, y) {
if (x == null || y == null) {
x = this.x;
y = this.y;
}
var width = this.width + (this.width * this.z), // scaled width
height = this.height + (this.height * this.z), // scaled height
cx = x * (width + dist) - y * (width + dist) / 2,
cy = y * (3/4 * height + dist),
ctx = this.ctx;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.moveTo(cx, cy - height/2);
ctx.lineTo(cx + width/2, cy - height/4);
ctx.lineTo(cx + width/2, cy + height/4);
ctx.lineTo(cx, cy + height/2);
ctx.lineTo(cx - width/2, cy + height/4);
ctx.lineTo(cx - width/2, cy - height/4);
ctx.lineTo(cx, cy - height/2);
ctx.fill();
};
Usage
var canvas = document.getElementById('tutorial');
var ctx = canvas.getContext('2d');
var dist = 2;
// Create Hexagons
var size = {
width: 70,
height: 80
};
var hexes = [
new Hexagon(ctx, "rgb(0, 0, 255)", [1, 1], size),
new Hexagon(ctx, "rgb(0, 0, 225)", [3, 1], size),
new Hexagon(ctx, "rgb(0, 0, 195)", [4, 1], size),
new Hexagon(ctx, "rgb(0, 0, 165)", [6, 1], size),
new Hexagon(ctx, "rgb(0, 225, 0)", [3, 2], size),
new Hexagon(ctx, "rgb(0, 225, 0)", [4, 2], size),
new Hexagon(ctx, "rgb(0, 195, 0)", [5, 2], size)
];
function draw() {
for (var i = hexes.length; i--;) {
hexes[i].draw();
}
}
You might want to look at mrdoob's three.js:
http://github.com/mrdoob/three.js/
Related
So I looked at the answer here: Draw arrow head in canvas using Angle
But it didn't seem to do it the way I wanted. I definitely do not want to use rotate, what I would like, is based on an Angle in degrees (0 being up on the screen, 180 being down, etc) is draw an arrow pointing in that direction.
Now I slept through trig in highschool so the correct usage of Rads, Sin and Cos are... well, they elude me :(.
Anyways, I have the angle already computed, and based on that I want to draw like the following:
The top one is at 0 degrees in my computation, the lower one 90 degrees.
I'm using a 2d canvas as my draw surface.
Inspired by the trigonometry of this answer I made a line + two smaller lines for the arrow part.
const size = 200;
var canvas = document.querySelector("canvas")
canvas.width = size;
canvas.height = size;
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, size, size);
ctx.strokeStyle = "red"
function lineToAngle(ctx, x1, y1, length, angle) {
angle = (angle - 90) * Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.fill();
return {
x: x2,
y: y2
};
}
function draw_arrow(ctx, x1, y1, length, angle) {
var pos = lineToAngle(ctx, x1, y1, length, angle);
lineToAngle(ctx, pos.x, pos.y, 10, angle - 135);
lineToAngle(ctx, pos.x, pos.y, 10, angle + 135);
}
var pos = draw_arrow(ctx, 50, 50, 50, 30);
ctx.strokeStyle = "blue"
for (var angle = 0; angle <= 360; angle += 30) {
draw_arrow(ctx, 100, 100, 60, angle);
}
<canvas></canvas>
Overkill maybe?
This may help or may not.
Rather than create the arrow (shape) with a set of draw calls you can create a
Path2D to hold the shape with the helper function Path (in demo code below) that converts a set of arrays (each array a sub path) to a Path2D object. For example. const arrow = Path([[0, 0, 180, 0], [160, -10, 180, 0, 160, 10]]); a simple line arrow. To draw the path call ctx.stroke(arrow) or ctx.fill(arrow);
Define a style using Style eg const red = Style(...["#F00", , 2, "round"])
Position, rotate, and scale
Then the function drawPath(ctx, path, style, centerX, centerY, deg, scale) where...
ctx context to draw on.
path path to draw
style style to use,
centerX, centerY point to rotate around
deg angle in degrees from 0 deg pointing up, and positive values moving clockwize
scale lets you set the scale and defaults to 1.
Example use
// draw red arrow pointing up
const arrow = Path([[0, 0, 180, 0], [160, -10, 180, 0, 160, 10]]);
const red = Style(...["#F00", , 2, "round"]);
drawPath(ctx, arrow, red, 200, 200, 0);
Demo
Working example draw 6 arrows using 3 styles rotating around center of canvas.
const Path = (paths) => {
var j = 0, xx, yy, path = new Path2D();;
for (const subPath of paths) {
j = 0;
while (j < subPath.length) {
const [x, y] = [subPath[j++], subPath[j++]];
j === 2 ?
path.moveTo(...([xx, yy] = [x, y])) :
xx === x && yy === y ? path.closePath() : path.lineTo(x, y);
}
}
return path;
}
const Style = (strokeStyle, fillStyle, lineWidth, lineJoin = "bevel") => ({strokeStyle, fillStyle, lineWidth, lineJoin});
const styles = {
redLine: Style(...["#F00", , 6, "round"]),
greenFill: Style(...[ , "#0A0"]),
fillAndLine: Style(...["#000", "#0AF", 2, "round"]),
};
const paths = {
lineArrow: Path([ [10, 0, 180, 0], [160, -10, 180, 0, 160, 10] ]),
fatArrow: Path([ [10, -5, 180, -5, 170, -15, 200, 0, 170, 15, 180, 5, 10, 5, 10, -5] ]),
diamondArrow: Path([ [60, 0, 100, -15, 150, 0, 100, 15, 60, 0] ]),
};
requestAnimationFrame(mainLoop);
const [W, H, ctx] = [can.width, can.height, can.getContext("2d")];
const DEG2RAD = Math.PI / 180, DEG_0_OFFSET = -90;
function drawPath(ctx, path, style, centerX, centerY, deg, scale = 1) {
const rad = (deg + DEG_0_OFFSET) * DEG2RAD;
const [ax, ay] = [Math.cos(rad) * scale, Math.sin(rad) * scale];
ctx.setTransform(ax, ay, -ay, ax, centerX, centerY);
Object.assign(ctx, style);
style.fillStyle && ctx.fill(path);
style.strokeStyle && ctx.stroke(path);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
function mainLoop(time) {
ctx.clearRect(0, 0, W, H);
drawPath(ctx, paths.fatArrow, styles.fillAndLine, W * 0.5, H * 0.5, (time / 15000) * 360, 0.5);
drawPath(ctx, paths.fatArrow, styles.greenFill, W * 0.5, H * 0.5, (time / 20000) * 360);
drawPath(ctx, paths.diamondArrow, styles.fillAndLine, W * 0.5, H * 0.5, (time / 30000) * 360, 0.5);
drawPath(ctx, paths.diamondArrow, styles.greenFill, W * 0.5, H * 0.5, (time / 60000) * 360);
drawPath(ctx, paths.lineArrow, styles.redLine, W * 0.5, H * 0.5, (time / 5000) * 360, 0.9);
drawPath(ctx, paths.lineArrow, styles.redLine, W * 0.5, H * 0.5, (time / 10000) * 360);
requestAnimationFrame(mainLoop);
}
<canvas id="can" width="400" height="400"></canvas>
So, in my car-parking based webgame, I want it so the car goes over the parking lines in the html canvas, but nothing is working. Obviously, I tried putting the car drawing in javascript before the parking lot, but that doesn't work. I have the game at https://parkingmaster.w3spaces.com try parking over the lines, they'll go over the car.
JavaScript:
ctx = canvas.getContext('2d'),
last = performance.now(),
x = 0,
animate;
var y = 20;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.rect(x, y, 110,65);
ctx.rect(x + 5, y + 58, 30,10);
ctx.rect(x + 75, y + 58, 30,10)
ctx.rect(x + 5, y - 3, 30,10);
ctx.rect(x + 75, y -3, 30,10)
ctx.fillStyle = "#000000";
ctx.fill();
x += 5;
animate = requestAnimationFrame(draw);
if (x > 500) {
GameOver();
}
parkinglot(draw);
document.getElementById("speedbar").innerHTML = '5';
};
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
last = performance.now(),
x = 0,
animate;
var y = 20;
function mouseRightDown(draw) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.rect(x, y, 110,65);
ctx.rect(x + 5, y + 58, 30,10);
ctx.rect(x + 75, y + 58, 30,10)
ctx.rect(x + 5, y - 3, 30,10);
ctx.rect(x + 75, y -3, 30,10)
ctx.fillStyle = "#000000";
ctx.fill();
y += 1.5;
x += 0.3;
if (y > canvas.height - 66) {
GameOver();
}
animate2 = requestAnimationFrame(mouseRightDown);
parkinglot(draw);
}
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
last = performance.now(),
x = 0,
animate;
var y = 20;
function mouseLeftDown(draw) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.rect(x, y, 110,65);
ctx.rect(x + 5, y + 58, 30,10);
ctx.rect(x + 75, y + 58, 30,10)
ctx.rect(x + 5, y - 3, 30,10);
ctx.rect(x + 75, y -3, 30,10)
ctx.fillStyle = "#000000";
ctx.fill();
y += -1.5;
x += 0.3;
if (y < -1) {
GameOver();
}
animate3 = requestAnimationFrame(mouseLeftDown);
parkinglot(draw);
}
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
last = performance.now(),
x = 0,
animate;
var y = 20;
function mouseReverseDown(draw) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.rect(x, y, 110,65);
ctx.rect(x + 5, y + 58, 30,10);
ctx.rect(x + 75, y + 58, 30,10)
ctx.rect(x + 5, y - 3, 30,10);
ctx.rect(x + 75, y -3, 30,10)
ctx.fillStyle = "#000000";
ctx.fill();
x += -4;
if (x < -2) {
GameOver();
}
document.getElementById("speedbar").innerHTML = '4';
animate4 = requestAnimationFrame(mouseReverseDown);
parkinglot(draw);
}
function GameOver() {
var gameOverAlert = alert("Game Over");
var gameOverAlert = false;
document.getElementById("status-button-bar").innerHTML = '';
var gameOverAlert = true;
if (gameOverAlert == true); {
var gameOverAlert = false;
}
window.location.href = "https://parkingmaster.w3spaces.com/index.html?bypass-cache=1624983081";
document.getElementById("hidebuttons").innerHTML = '';
}
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d');
function parkinglot(draw) {
ctx.fillStyle = '#fad201';
ctx.fillRect(590, 0, 10, canvas.height);
ctx.fillRect(450, 0, 140, 10);
ctx.fillRect(450, 90, 140, 10);
ctx.fillRect(450, 180, 140, 10);
// outline
ctx.fillStyle = '#00FF00';
ctx.fillRect(450, 270, 20, 10);
ctx.fillRect(480, 270, 20, 10);
ctx.fillRect(510, 270, 20, 10);
ctx.fillRect(540, 270, 20, 10);
ctx.fillRect(570, 270, 20, 10);
ctx.fillStyle = '#00FF00';
ctx.fillRect(450, 360, 20, 10);
ctx.fillRect(480, 360, 20, 10);
ctx.fillRect(510, 360, 20, 10);
ctx.fillRect(540, 360, 20, 10);
ctx.fillRect(570, 360, 20, 10);
}
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d');
function cars(draw) {
ctx.fillStyle = '#000000';
ctx.fillRect(0, 20, 110,65);
ctx.fillRect(5, 78, 30,10);
ctx.fillRect(75, 78, 30,10)
ctx.fillRect(5, 17, 30,10);
ctx.fillRect(75, 17, 30,10)
}
Your code ends with:
cars(draw);
parkinglot(draw);
So you're drawing the cars, and then the parking-lot.
Canvas draws new elements on op of existing elements, therefore your parking-lot will be placed above the car drawings.
So'll you'll probably just need to change the order of drawing:
parkinglot(draw);
cars(draw);
Example were we draw a red line, and then a green one. You'll see that the last drawn line will be visible in the intersection:
let c = document.getElementsByTagName('canvas')[0];
c.width = 200;
c.height = 200;
let x = c.getContext('2d');
x.lineWidth = 5;
// Red
x.beginPath();
x.moveTo(0, 0);
x.lineTo(200, 200);
x.strokeStyle = "#ff5500";
x.stroke();
// Green
x.beginPath();
x.moveTo(200, 0);
x.lineTo(0, 200);
x.strokeStyle = "#008000";
x.stroke();
canvas { border: 1px solid black; }
<canvas></canvas>
I'd like to draw an ellipse given a cx and cy position-property and a width and height property of the ellipse itself.
Below you can find some working code for this setup:
But now I want to generate a kind of "progress display" by painting a percentage (from 0 to 100) of the ellipse instead of the complete ellipse.
I have attached a graphic here to illustrate the whole thing:
I don't really have a clear idea how to do that. I would prefer a solution where I can do without resizing the canvas - just for performance reasons and I hope someone has a good idea how to solve my problem.
let canvas = document.getElementById("canvas")
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 280;
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height)
let ellipse = function(cx, cy, w, h) {
let lx = cx - w / 2,
rx = cx + w / 2,
ty = cy - h / 2,
by = cy + h / 2;
let magic = 0.551784;
let xmagic = magic * w / 2,
ymagic = h * magic / 2;
let region = new Path2D();
region.moveTo(cx, ty);
region.bezierCurveTo(cx + xmagic, ty, rx, cy - ymagic, rx, cy);
region.bezierCurveTo(rx, cy + ymagic, cx + xmagic, by, cx, by);
region.bezierCurveTo(cx - xmagic, by, lx, cy + ymagic, lx, cy);
region.bezierCurveTo(lx, cy - ymagic, cx - xmagic, ty, cx, ty);
ctx.strokeStyle = "red";
ctx.lineWidth = "10";
region.closePath();
ctx.stroke(region);
}
ellipse(canvas.width / 2, canvas.height / 2, 300, 120)
<canvas id="canvas"></canvas>
You can use the built-in function ctx.ellipse - first we draw the green line as a full ellipse. Next, draw the red partial ellipse on top:
let canvas = document.getElementById("canvas")
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 280;
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height)
function ellipse(ctx, color, x,y, w, h, thickness, angle) {
ctx.strokeStyle = color;
ctx.beginPath();
ctx.ellipse(canvas.width / 2, canvas.height / 2, h/2,w/2, Math.PI*3/2, 0, angle);
ctx.lineWidth = thickness;
ctx.stroke();
}
function ell(percent) {
let x= canvas.width / 2;
let y= canvas.height / 2;
let w=300;
let h=120;
let th = 10; // example thickness 10px
ellipse(ctx, '#608a32', x,y, w, h, th, Math.PI*2);
ellipse(ctx, '#ed3833', x,y , w, h, th+.3, 2*Math.PI*percent/100);
}
ell(90); // here we start draw for 90%
<canvas id="canvas"></canvas>
You can draw the ellipse with a bit of trigonometry
let canvas = document.getElementById("canvas")
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 170;
let ellipse = function(cx, cy, ds, de, w, h, color) {
for (var i = ds; i < de; i ++) {
var angle = i * ((Math.PI * 2) / 360);
var x = Math.cos(angle) * w;
var y = Math.sin(angle) * h;
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(cx+ x, cy+y, 6, 0, 2 * Math.PI);
ctx.fill();
}
}
let draw = function(cx, cy, ds, de, w, h, color) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
delta += 10
if (delta > 350) delta = 40
hw = canvas.width / 2
hh = canvas.height / 2
ellipse(hw, hh, 0, 360, 150, 60, "red")
ellipse(hw, hh, 0, delta, 150, 60, "blue")
ctx.font = "80px Arial";
ctx.fillStyle = "green";
ctx.fillText(Math.round(delta/3.6) + "%", hw-70, hh+30);
}
delta = 90
setInterval(draw, 100)
<canvas id="canvas"></canvas>
Once you have a nice function you can animate it
I just had a quick thought in mind of drawing a chessboard using JS and Canvas, and I have this code that draws the boxes alright with for loops.
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var x, y,
boxWidth = 30,
boxHeight = 30;
for (x = 0; x < canvas.width; x += boxWidth) {
for (y = 0; y < canvas.height; y += boxHeight) {
ctx.beginPath();
ctx.rect(x, y, boxWidth, boxHeight);
ctx.stroke();
ctx.closePath();
}
}
<canvas id="canvas" width="240" height="240"></canvas>
Now I'm wondering how I can access each odd box on the axes to change their fill colors (e.g. black, white, black, white, and so on).
I know using global variables isn't the best way, but this is a very small project and I just want to get some logic on how I can alternate the colors of the chessboard. Your help is very much appreciated!
You could also try only incrementing your values by 1 (instead of boxWidth), which would make it simpler to check if they are even or odd. Then you would need to either scale or multiply by boxWidth and boxHeight:
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var x, y,
boxWidth = 30,
boxHeight = 30;
var numbRows = Math.floor(canvas.width / boxWidth),
numbCols = Math.floor(canvas.height / boxHeight);
ctx.save();
ctx.scale(boxWidth, boxHeight);
for (x = 0; x < numbRows; x++) {
for (y = 0; y < numbCols; y++) {
if ((x+y) % 2 == 0) ctx.fillStyle = 'white';
else ctx.fillStyle = 'black';
ctx.beginPath();
ctx.rect(x, y, boxWidth, boxHeight);
ctx.stroke();
ctx.closePath();
}
}
ctx.restore();
You can use fillRect to do so like this:
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var x, y,
boxWidth = 30,
boxHeight = 30;
for (x = 0; x < canvas.width; x += boxWidth) {
for (y = 0; y < canvas.height; y += boxHeight) {
ctx.fillStyle = (x / boxWidth + y / boxHeight) % 2 === 0? "white": "black"; // determine which color to use depending on the index of x (x / boxWidth) an the index of y (y / boxHeight)
ctx.fillRect(x, y, boxWidth, boxHeight);
}
}
<canvas id="canvas" width="240" height="240"></canvas>
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var x, y,
boxWidth = 30,
boxHeight = 30;
for (x = 0; x < canvas.width; x += boxWidth) {
for (y = 0; y < canvas.height; y += boxHeight) {
ctx.beginPath();
ctx.rect(x, y, boxWidth, boxHeight);
// fill odd boxes
(x/boxWidth + y/boxHeight) % 2 && ctx.fill()
ctx.stroke();
ctx.closePath();
}
}
<canvas id="canvas" width="240" height="240"></canvas>
Alternatives to the for loop
Another way without a loop, draw the pattern 2 by 2 square in top corner then repeat that by copying the canvas onto itself.
First create the top 2 by 2 square then fill rest of board with copies.
First 2 by 2 to 4 by 2
then 4 by 2 to 8 by 2
then 8 by 2 to 8 by 4
then 8 by 4 to 8 by 8
Example
const w= 100;
canvas.height = canvas.width = w * 8;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, w + w, w + w);
ctx.fillStyle = "white";
ctx.fillRect(0, 0, w, w);
ctx.fillRect(w, w, w, w);
ctx.drawImage(canvas, 0, 0, w * 2, w * 2, w * 2, y , w * 2, w * 2);
ctx.drawImage(canvas, 0, 0, w * 4, w * 2, w * 4, y , w * 4, w * 2);
ctx.drawImage(canvas, 0, 0, w * 8, w * 2, 0 , w * 2, w * 8, w * 2);
ctx.drawImage(canvas, 0, 0, w * 8, w * 4, 0 , w * 4, w * 8, w * 4);
Thus it gets drawn in 7 render calls, if the grid was larger then 2 more calls for 16 by 16, and every doubling in size only needs two more calls.
The pattern can be very complex but not create excessive render stress as in the next example that has shadows and different composite calls.
const squareSize = 72;
const boardSize = 8;
const borderSize = 8;
canvas.height = canvas.width = squareSize * boardSize + borderSize * 2;
const ctx = canvas.getContext("2d");
var x = borderSize;
var y = x;
var w = squareSize;
drawSquare(3, 3, canvas.width - 6, "black", "#F97");
drawSquare(x, y, w, "white", "#964");
drawSquare(w + x, y, w, "black", "#745");
ctx.drawImage(canvas, x, y, w, w, x + w, y + w, w, w);
ctx.drawImage(canvas, x + w, y, w, w, x, y + w, w, w);
ctx.drawImage(canvas, x, y, w * 2, w * 2, x + w * 2, y, w * 2, w * 2);
ctx.drawImage(canvas, x, y, w * 4, w * 2, x + w * 4, y, w * 4, w * 2);
ctx.drawImage(canvas, x, y, w * 8, w * 2, x, y + w * 2, w * 8, w * 2);
ctx.drawImage(canvas, x, y, w * 8, w * 4, x, y + w * 4, w * 8, w * 4);
drawSquare(0,0,canvas.width,"rgba(0,0,0,0.0)","rgba(0,0,0,0.05)");
// done.
// this function is only called twice.
function drawSquare(x,y,size,color,color2){
ctx.save();
ctx.shadowColor = color2;
ctx.shadowBlur = size * 0.2;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.beginPath();
ctx.rect(x,y,size,size);
ctx.clip();
ctx.lineWidth = size;
ctx.fillStyle = color;
ctx.fillRect(x,y,size,size);
ctx.globalAlpha = 0.5;
ctx.strokeRect(x - size / 2,y - size / 2, size * 2, size * 2);
ctx.shadowBlur = size * 0.5;
ctx.strokeRect(x - size / 2,y - size / 2, size * 2, size * 2);
ctx.shadowColor = "rgba(0,0,0,0)";
ctx.shadowBlur = 0;
ctx.globalAlpha = 1;
ctx.strokeStyle = color2;
ctx.lineWidth = 2;
ctx.strokeRect(x+1,y+1,size -2,size-2);
ctx.globalAlpha = 0.75;
ctx.fillRect(x+1,y+1,size-2,size-2);
ctx.globalCompositeOperation = "screen";
ctx.fillStyle = "white";
ctx.globalAlpha = 0.1;
ctx.fillRect(x,y,4,size);
ctx.fillRect(x,y,2,size);
ctx.fillRect(x+4,y,size-4,4);
ctx.fillRect(x+2,y,size-2,2);
ctx.restore();
}
canvas { border : 2px solid black; }
<canvas id="canvas" ></canvas>
Use a pattern style.
Create an offscreen canvas to hold the pattern of the top 2 by 2, draw the pattern, then assign the fillStyle of the on screen canvas to a new pattern created from the off screen canvas and fill the whole canvas.
const w = 72;
const patCan = document.createElement("canvas");
patCan.height = patCan.width = w * 2;
var ctx = patCan.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, w + w, w + w);
ctx.fillStyle = "white";
ctx.fillRect(0, 0, w, w);
ctx.fillRect(w, w, w, w);
// setup display canvas
canvas.height = canvas.width = w * 8;
var ctx = canvas.getContext("2d");
ctx.fillStyle = ctx.createPattern(patCan, "repeat");
ctx.fillRect(0, 0, w * 8, w * 8);
canvas { border : 8px solid green; }
<canvas id="canvas" ></canvas>
Rendering with 3 lines of code
Here's a neat little trick you can use to draw a chess board:
var ctx = c.getContext("2d");
for(var x = 0; x < c.width; x += c.width / 4) ctx.fillRect(x, 0, c.width/8, c.height);
ctx.globalCompositeOperation = "xor"; // toggle alpha channel for every 2nd line
for(var y = 0; y < c.height; y += c.height / 4) ctx.fillRect(0, y, c.width, c.height/8);
<canvas id=c width=600 height=600></canvas>
We're using canvas' size to detemine the grid size. You can of course change this and offset to anything you like. You'd still use the divisors 4 (2 cells) and 8 (1 cell) with the actual width and height.
The first step draws vertical black stripes every other column. Then we toggle alpha channel for every other row knowing that the default color is black (rgba(0,0,0,0)) using the "xor" composite mode which toggles the alpha channel.
Just remember to start with a empty canvas (which you probably are anyways due to the need to redraw moves) and to set back composite mode to "source-over" after drawing the board.
If you'd like to change the color itself simply add an extra step at the end:
ctx.globalCompositeOperation = "source-atop"; // will draw on top of what is filled
ctx.fillStyle = "#09a";
ctx.fillRect(0, 0, c.width, c.height);
The fillStyle and fillRect() can be replaced or used with an image, pattern, gradient etc.
To fill the white background simply use composite mode "destination-over" (will draw behind anything filled using the alpha channel), then draw for background.
An alternative is to use a toggle switch when filling each cell one by one:
var ctx = c.getContext("2d");
var toggle = false;
ctx.beginPath();
for(var y=0; y < c.height; y += c.height / 8) {
toggle = !toggle; // toggle for each row so they don't line up
for(var x=0; x < c.width; x += c.width / 8) {
// toggle for each cell and check, only draw if toggle = true
if (toggle = !toggle) ctx.rect(x, y, c.width / 8, c.height / 8);
}
}
ctx.fill(); // remember to use beginPath() for consecutive draw ops
<canvas id=c width=600 height=600></canvas>
Access Logic
To know if you're inside a cell you would simply calculate the mouse position relative to the canvas (see this answer on how to do that) and then quantize (using pseudo variables here, replace with real):
var cellSize = boardWidth / 8; // assumes the board is 1:1 square
var pos = getMousePos(event); // see linked answer above
var cellX = Math.floor(pos.x / cellSize) * cellSize; // start of current cell X
var cellY = Math.floor(pos.y / cellSize) * cellSize; // start of current cell Y
(to get index of cell just drop the * cellSize part).
Example:
var ctx = c.getContext("2d"), x, y, w = c.width, h = c.height, cellSize = w / 8;
render();
ctx.lineWidth = 4; ctx.strokeStyle = "red"; ctx.setLineDash([7, 7]);
// non-optimized - in production only redraw when needed (cellX/Y changes)
c.onmousemove = function(e) {
render();
var cell = getCellPos(getMousePos(e));
if (cell.x >= 0 && cell.x < w && cell.y >=0 && cell.y < h)
ctx.strokeRect(cell.x + 2, cell.y + 2, cellSize - 4, cellSize - 4);
}
function getCellPos(pos) {
return {x: Math.floor(pos.x / cellSize) * cellSize,
y: Math.floor(pos.y / cellSize) * cellSize}
}
function getMousePos(e) {
var rect = c.getBoundingClientRect();
return {x: e.clientX-rect.x, y: e.clientY-rect.y}
}
function render() {
ctx.clearRect(0, 0, w, h);
for(x = 0; x < w; x += w>>2) ctx.fillRect(x, 0, cellSize, c.height);
ctx.globalCompositeOperation = "xor"; // toggle alpha channel for every 2nd line
for(y = 0; y < h; y += h>>2) ctx.fillRect(0, y, w, cellSize);
ctx.globalCompositeOperation = "source-atop"; // fg
ctx.fillStyle = "#3c4168";
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = "destination-over"; // bg
ctx.fillStyle = "#eee";
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = "source-over"; // reset
}
body {background:#222;margin:20px 0 0 20px;}
<canvas id=c width=600 height=600></canvas>
I need to create a QAM constellation diagram via a WebPag. I will give a either X,Y or Complex numbers. I tried looking for some sample code that will help me make this plot but no success. The closest would be a scatter plot. Any suggestions?
Here's an example drawing of some x,y coordinates. The grid starts in the upper left corner at 0,0 with positive x going to the right and positive y going down.
function draw() {
var can = document.getElementById('can');
var ctx = can.getContext('2d');
var w = can.width;
var h = can.height;
ctx.fillStyle = "rgb(40,90,150)";
ctx.fillRect(0, 0, w, h);
var center_w = Math.floor(w / 2);
var center_h = Math.floor(h / 2);
ctx.beginPath();
ctx.strokeStyle = "rgb(255,255,255)"
// draw axis
ctx.moveTo(0, center_h);
ctx.lineTo(w, center_h);
ctx.moveTo(center_w, 0);
ctx.lineTo(center_w, h);
//draw circle
var radius = 20;
var start_angle = 0;
var end_angle = 360;
var to_radians = Math.PI / 180;
ctx.moveTo(center_w + radius, center_h);
ctx.arc(center_w, center_h, radius, start_angle * to_radians, end_angle * to_radians);
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = "rgb(255,255,0)";
// plot coords
var coords = [
[center_w, center_h],
[10, 10],
[10, 60],
[10, 110],
[10, 160],
[60, 10],
[110, 10],
[160, 10]
];
for (var i = 0; i < coords.length; i++) {
var x = coords[i][0];
var y = coords[i][1];
ctx.beginPath();
var radius = 2;
ctx.moveTo(x + radius, y)
ctx.arc(x, y, radius, 0 * to_radians, 360 * to_radians);
ctx.fill();
ctx.fillText(" " + x + ", " + y, x, y);
}
}
draw();
<canvas id="can" width="200" height="200"></canvas>