I've created a canvas pattern and want to repeat the pattern diagonally through the whole web page. Since the repeat property only support repeat-x, repeat-y or repeat both direction, I've set it to 'no-repeat' for now and tried to use offset or translate to move my pattern diagonally, but didn't work out.
Here is what I got for now:
enter image description here
Here is what I want to accomplish:
enter image description here
I'm just trying to mimic the effect, doesn't need to be exact the same.
Can someone tell me how to continue my pattern diagonally? Thanks a lot!
Here are some of my codes:
var patternCanvas = document.createElement('canvas');
var patternContext = patternCanvas.getContext('2d');
patternCanvas.width = 350;
patternCanvas.height = 350;
patternContext.fillStyle = "orange";
patternContext.fillRect(0, 50, 150, 50);
patternContext.fillRect(50, 0, 50, 150);
patternContext.fillStyle = "black";
patternContext.fillRect(100, 100, 150, 50);
patternContext.fillRect(150, 50, 50, 150);
patternContext.fillStyle = "green";
patternContext.fillRect(200, 150, 150, 50);
patternContext.fillRect(250, 100, 50, 150);
patternContext.fillStyle = "darkred";
patternContext.fillRect(0, 100, 50, 150);
patternContext.fillRect(0, 150, 150, 50);
patternContext.fillStyle = "blue";
patternContext.fillRect(100, 150, 50, 150);
patternContext.fillRect(50, 200, 150, 50);
patternContext.fillStyle = "yellow";
patternContext.fillRect(200, 200, 50, 150);
patternContext.fillRect(150, 250, 150, 50);
var canvas = document.getElementById("myCanvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var context = canvas.getContext('2d');
var pattern = context.createPattern(patternCanvas, 'no-repeat');
context.fillStyle = pattern;
context.fillRect(0, 0, 350, 350);
<canvas id="myCanvas"></canvas>
This solution treats the canvas as a blank slate rather than as a place to copy/paste a pattern.
It creates the appearance of a repeating pattern by dividing the canvas into squares and coloring each square according to its position in a virtual grid.
Thanks to irchans for help with the math.
const
// Creates a canvas, and a context
canvas = document.getElementById("myCanvas"),
context = canvas.getContext('2d'),
// Configures colors, unit-square size, and the number of unit squares to draw
colors = "blue,yellow,darkred,green,orange,black".split(","),
unit = 50,
gridDimensionX = 10,
gridDimensionY = 10;
// Makes the canvas wide enough for its content
canvas.width = unit * gridDimensionX;
canvas.height = unit * gridDimensionY;
// Builds a grid of squares, each of which is assigned a color
const grid = makeGrid(gridDimensionX, gridDimensionY, colors);
// Loops through the grid and draws each square
drawGrid(grid, context, unit);
// Defines the `makeGrid` function
function makeGrid(gridDimensionX, gridDimensionY, colors){
const grid = [];
for(let y = 0; y < gridDimensionY; y++){
const row = [];
for(let x = 0; x < gridDimensionX; x++){
// Assigns coordinates to each not-yet-drawn square, along two axes
// (rotated 60 degrees from the vertical and horizontal axes)
// and groups squares according to these coordinates
cell = {
slantyRowGrp: Math.round((2 * y - x) / 5, 0),
slantyColGrp: Math.round((y + 2 * x) / 5, 0)
}
// Assigns a color to each square based on its 'slanty' grouping
cell.colorIndex = (cell.slantyRowGrp + 2 * cell.slantyColGrp) % colors.length;
cell.color = colors[cell.colorIndex];
// Adds the cell to the row
row.push(cell);
}
// Adds the completed row to the grid
grid.push(row);
}
// Returns the completed grid
return grid;
}
// Defines the `drawGrid` function
function drawGrid(grid, context, unit){
grid.forEach( (row, y) => {
row.forEach( (cell, x) => {
// Fills each square with its assigned color
context.fillStyle = cell.color;
context.fillRect(unit * x, unit * y, unit, unit);
// Displays the 'slanty' row and column group numbers
/*
context.fillStyle = "lightgrey";
context.fillText(
`${cell.slantyRowGrp}; ${cell.slantyColGrp}`,
unit * x + unit/2.5,
unit * y + unit/2
);
*/
});
});
}
<canvas id="myCanvas"></canvas>
It took quite a lot of effort to achieve. It's a deceptively complex question.
Each position moves by something like x = x + (iterationY/3) * 2, y = iterationX although my code has denormalized iteration steps for ix and iy of 1/3, to make it easier to reason about moving by cross blocks e.g. 1/3 width or height of a cross.
To assign an id to each cross for colouring, I take a row and column where x/y iterations have a step of 1 as row = iterationX % 2 and col = iterationY % 3, which gives row going from 0, 1 and so on, and col going from 0, 1, 2 and repeating. In this case I assign col a weight of 2, so the id = row+(col*2), to ensure each id is unique. Finally, I define an array of colours which can be referenced by this id.
const canvas = document.getElementById("canv");
const ctx = canvas.getContext('2d');
const w = window.innerWidth;
const h = window.innerHeight;
const s = 80;//size
const bs = s / 3;//block size
const gs = Math.max(w, h)/s;//grid size
canvas.width = w;
canvas.height = h;
let yAcc = 0;
let jx = 0;
let jy = 0;
const getColour = (jx, jy) => {
const row = (jx%2);//0, 1
const col = (jy % 3);//0, 1, 2
//00, 10, 01, 11, 02, 12
//0 , 1 , 2 , 3 , 4 , 5
const id = row + (col*2);
const colours = ['orange', 'blue', 'black', 'yellow', 'green', 'red']
return colours[id];
}
for(let ix = 0; ix < gs; ix+=1/3){
for(let iy = 0; iy < gs; iy+=1/3){
const colour = getColour(jx, jy);
let x = ix+iy*2-(gs);
let y = iy+ix*3-(gs);
ctx.fillStyle = colour;
ctx.beginPath();
ctx.lineWidth = 2;
ctx.rect(x * bs * 3, y * bs * 3 + bs, bs * 3, bs);
ctx.rect(x * bs * 3 + bs, y * bs * 3, bs, bs * 3);
ctx.fill();
ctx.closePath();
jy ++;
}
jx ++;
}
<canvas id="canv"></canvas>
Related
I am trying to achieve a tracing effect where the lines have a faded trail. The way I am trying to do it is simply by drawing the solid background once, and then on further frames draw a transparent background before drawing the new lines, so that you can still see a little of the image before it.
The issue is that I do want the lines to fade out completely after some time, but they seem to leave a permanent after image, even after drawing over them repeatedly.
I've tried setting different globalCompositeOperation(s) and it seemed like I was barking up the wrong tree there.
This code is called once
//initiate trace bg
traceBuffer.getContext("2d").fillStyle = "rgba(0, 30, 50, 1)";
traceBuffer.getContext("2d").fillRect(0, 0, traceBuffer.width, traceBuffer.height);
then inside the setInterval function it calls
//draw transparent background
ctx.fillStyle = "rgba(0, 30, 50, 0.04)";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
//set drawing settings
ctx.strokeStyle = "#AAAAAA";
ctx.lineWidth = 4;
for (let i = 0; i < tracer.layer2.length; i++){
ctx.beginPath();
ctx.moveTo(newX, newY);
ctx.lineTo(oldX, oldY);
ctx.stroke();
}
Here's an example: https://i.imgur.com/QTkeIVf.png
On the left is what I am currently getting, and on the right is the edit of what I actually want to happen.
This is how I would do it. I would build a history of the particles moving on the track. The older the position the smaller the value of the alpha value for the fill. Also for a nicer effect I would reduce the size of the circle.
I hope this is what you need.
PS: I would have loved to have your curve. Since I don't have it I've drawn a different one.
const hypotrochoid = document.getElementById("hypotrochoid");
const ctx = hypotrochoid.getContext("2d");
const cw = (hypotrochoid.width = 300);
const ch = (hypotrochoid.height = 300);
const cx = cw / 2,
cy = ch / 2;
ctx.lineWidth = 1;
ctx.strokeStyle = "#d9d9d9";
// variables for the hypotrochoid
let a = 90;
let b = 15;
let h = 50;
// an array where to save the points used to draw the track
let track = [];
//add points to the track array. This will be used to draw the track for the particles
for (var t = 0; t < 2 * Math.PI; t += 0.01) {
let o = {};
o.x = cx + (a - b) * Math.cos(t) + h * Math.cos((a - b) / b * t);
o.y = cy + (a - b) * Math.sin(t) - h * Math.sin((a - b) / b * t);
track.push(o);
}
// a function to draw the track
function drawTrack(ry) {
ctx.beginPath();
ctx.moveTo(ry[0].x, ry[0].y);
for (let t = 1; t < ry.length; t++) {
ctx.lineTo(ry[t].x, ry[t].y);
}
ctx.closePath();
ctx.stroke();
}
// a class of points that are moving on the track
class Point {
constructor(pos) {
this.pos = pos;
this.r = 3;//the radius of the circle
this.history = [];
this.historyLength = 40;
}
update(newPos) {
let old_pos = {};
old_pos.x = this.pos.x;
old_pos.y = this.pos.y;
//save the old position in the history array
this.history.push(old_pos);
//if the length of the track is longer than the max length allowed remove the extra elements
if (this.history.length > this.historyLength) {
this.history.shift();
}
//gry the new position on the track
this.pos = newPos;
}
draw() {
for (let i = 0; i < this.history.length; i++) {
//calculate the alpha value for every element on the history array
let alp = i * 1 / this.history.length;
// set the fill style
ctx.fillStyle = `rgba(0,0,0,${alp})`;
//draw an arc
ctx.beginPath();
ctx.arc(
this.history[i].x,
this.history[i].y,
this.r * alp,
0,
2 * Math.PI
);
ctx.fill();
}
}
}
// 2 points on the track
let p = new Point(track[0]);
let p1 = new Point(track[~~(track.length / 2)]);
let frames = 0;
let n, n1;
function Draw() {
requestAnimationFrame(Draw);
ctx.clearRect(0, 0, cw, ch);
//indexes for the track position
n = frames % track.length;
n1 = (~~(track.length / 2) + frames) % track.length;
//draw the track
drawTrack(track);
// update and draw the first point
p.update(track[n]);
p.draw();
// update and draw the second point
p1.update(track[n1]);
p1.draw();
//increase the frames counter
frames++;
}
Draw();
canvas{border:1px solid}
<canvas id="hypotrochoid"></canvas>
I have a canvas and want to dynamically get the color of a pixel. Im using getImageData() to get an array containing the colors. I only want to call getImageData() once and get all the color data from it once. This is because in my use case I will loop over an array of size 1 million containing of x, y coordinates for the canvas.
However I cannot figure out how to get the color from the image array with the x, y coordinates. Here's my code:
var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
context.beginPath();
context.rect(0, 0, 200, 200);
context.fillStyle = 'black';
context.fill();
context.closePath();
context.beginPath();
context.rect(50, 50, 50, 50);
context.fillStyle = 'red';
context.fill();
context.closePath();
var imageData = context.getImageData(0, 0, 200, 200).data;
console.log('imageData is ', imageData);
for(var x = 0; x < 10; x++) {
var x = Math.floor(Math.random() * 200);
var y = Math.floor(Math.random() * 200);
console.log('x, y is ', x, y);
// how to I get the color here?
var color = Uint8ClampedArray[x][y];
console.log('color is ', color);
}
I get an error at the line:
var color = Uint8ClampedArray[x][y];
All other solutions involve calling getImageData() each time on the coordinate with a width and height of 1. But I dont want to do this, preferring to query imageData to get the color....
Fiddle is here.
To get a pixel
var x = 100;
var y = 100;
var imageData = context.getImageData(0, 0, 200, 200);
var index = (x + y * imageData.width) * 4;
var r = imageData.data[index];
var g = imageData.data[index + 1];
var b = imageData.data[index + 2];
var a = imageData.data[index + 3];
There are 4 bytes per pixel, pixels are arranged in rows. So each row has 4 * the width bytes.
Thus you find the row or y
var rowStart = y * imageData.width * 4;
and to find the pixel at column x multiply by 4 and add to the start row index
var pixelIndex = rowStart + x * 4;
The next 4 bytes are the red, green, blue and alpha values of the pixel at x,y
Ive made a working circular graph using javascript, and Ive run into a problem.
The script fetches the id of the graph in order to work, but I would like multiple graphs.
So here is my question: How do I make the script compatible with multiple graphs? I tried fetching them by their class but that didnt seem to work.
Here is my code:
var el = document.getElementById('graph'); // get canvas
var options = {
percent: el.getAttribute('data-percent') || 25,
size: el.getAttribute('data-size') || 80,
lineWidth: el.getAttribute('data-line') || 5,
color: el.getAttribute('data-color') || 0,
rotate: el.getAttribute('data-rotate') || 0
}
var canvas = document.createElement('canvas');
var span = document.createElement('span');
span.textContent = options.percent + '%';
if (typeof(G_vmlCanvasManager) !== 'undefined') {
G_vmlCanvasManager.initElement(canvas);
}
var ctx = canvas.getContext('2d');
canvas.width = canvas.height = options.size;
el.appendChild(span);
el.appendChild(canvas);
ctx.translate(options.size / 2, options.size / 2); // change center
ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI); // rotate -90 deg
var radius = (options.size - options.lineWidth) / 2;
var drawCircle = function(color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = 'butt'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
};
drawCircle('#F7F7F7', options.lineWidth, 100 / 100);
drawCircle(options.color, options.lineWidth, options.percent / 100);
And this is the HTML:
<div class="chart" id="graph"
data-percent="92"
data-color="#1EA1FF"
data-size="110" data-line="3">
</div>
Thanks in advance.
You should be able to surround everything with a loop. You need to select all the elements with the same class into an array, then loop through that array. I also suggest setting the "el" variable inside the array, then you don't have to change your code:
var graphs = document.getElementsByClassName("chart");
for(var i = 0; i < graphs.length; i++)
{
var el = graphs[i];
//Now the rest of your code
var options = ...
}
"el" then becomes each element as the loop iterates.
I'm creating a map dynamically from a json file but the drawings end up out of screen because of the x,y. What I wanted to do is to move the all drawings to closer to the left and top of the map element that contains the drawing.
I tried couple of things like playing with the top in css but then the div goes on top of other controls. The other idea is to modify the existing coordinates and do some math with it but I want to know if there is a better approach.
Here what I have
var c=[];
c.push(["valu1",120,240]);
c.push(["valu1",130,240]);
c.push(["valu2",120,250]);
c.push(["valu3",130,250]);
c.push(["valu4",120,245]);
c.push(["valu5",130,245]);
//7k more rows will be here.
$("#refresh").on("click",function(){
getdata();
});
function getdata()
{
for (var i in c) {
var x = c[i][1]
var y = c[i][2]
Paint(x,y);
}
}
function Paint(x, y) {
var ctx, cv;
cv = document.getElementById('map');
ctx = cv.getContext('2d');
ctx.strokeStyle = '#666699';
ctx.lineWidth = 1;
ctx.strokeRect(x, y, 10, 5);
ctx.stroke();
}
notice the first row x = 120. what I would like to move closer to the top of my element.
here is the jsfiddle.
JSFIDDLE
Below is an implementation that displaces the points toward the upper left as far as possible. If the map is still too large, it will have to be scaled to fit the canvas.
Note that I have changed some variable names and streamlined the code in various places. To make the canvas larger, see the parameters at the beginning of the start() function.
var data = [
["a", 120, 240],
["b", 130, 240],
["c", 120, 250],
["d", 130, 250],
["e", 120, 245],
["f", 130, 245]
];
function drawBox(context, x, y, width, height) {
context.strokeStyle = '#666699';
context.strokeRect(x, y, width, height);
}
function start() {
// The following are the display parameters. Set them to your liking.
var boxWidth = 10, boxHeight = 5, lineWidth = 2,
canvasWidth = 800, canvasHeight = 600; // Canvas dimensions.
var container = document.getElementById('container'),
canvas = container.getElementsByTagName('canvas')[0],
context = canvas.getContext('2d');
context.lineWidth = lineWidth;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
container.style.width = canvasWidth + 'px';
container.style.height = canvasHeight + 'px';
var n = data.length,
minX = data[0][1],
minY = data[0][2];
for (var i = 1; i < n; ++i) {
var x = data[i][1],
y = data[i][2];
if (x < minX) {
minX = x;
}
if (y < minY) {
minY = y;
}
}
for (var i = 0; i < n; ++i) {
var x = data[i][1], y = data[i][2],
X = lineWidth + x - minX, // Displace the points to upper right.
Y = lineWidth + y - minY; // Add padding to avoid clipping lines.
drawBox(context, X, Y, boxWidth, boxHeight);
}
}
window.onload = start;
#container {
border: 3px solid #eee;
}
<div id="container"><canvas></canvas></div>
Somehow I cannot get it the canvas clear in my canvas tag, it only clear some portion and i have put canvas.witdh and height as the argument. Originally it should clear all of it when a button is clicked and clearGraph() function is triggered and redraw. Please do help me.
You can view it from here. http://jsfiddle.net/qH2Lr/1/
function init() {
// data sets -- set literally or obtain from an ajax call
var dataName = [ "You", "Competitors" ];
var dataValue = [ 2600, 4000];
// set these values for your data
numSamples = 2;
maxVal = 5000;
var stepSize = 1000;
var colHead = 50;
var rowHead = 60;
var margin = 10;
var header = "Millions"
var can = document.getElementById("can");
ctx = can.getContext("2d");
ctx.fillStyle = "black"
yScalar = (can.height - colHead - margin) / (maxVal);
xScalar = (can.width - rowHead) / (numSamples + 1);
ctx.strokeStyle = "rgba(128,128,255, 0.5)"; // light blue line
ctx.beginPath();
// print column header
ctx.font = "14pt Helvetica"
ctx.fillText(header, 0, colHead - margin);
// print row header and draw horizontal grid lines
ctx.font = "12pt Helvetica"
var count = 0;
for (scale = maxVal; scale >= 0; scale -= stepSize) {
y = colHead + (yScalar * count * stepSize);
ctx.fillText(scale, margin,y + margin);
ctx.moveTo(rowHead, y)
ctx.lineTo(can.width, y)
count++;
}
ctx.stroke();
// label samples ctx.shadowColor = 'rgba(128,128,128, 0.5)';
ctx.shadowOffsetX = 20;
ctx.shadowOffsetY = 1;
// translate to bottom of graph and scale x,y to match data
ctx.translate(0, can.height - margin);
ctx.font = "14pt Helvetica";
ctx.textBaseline = "bottom";
for (i = 0; i < 4; i++) {
calcY(dataValue[i]);
ctx.fillText(dataName[i], xScalar * (i + 1), y - margin);
}
// set a color and a shadow
ctx.fillStyle = "green";
ctx.scale(xScalar, -1 * yScalar);
// draw bars
for (i = 0; i < 4; i++) {
ctx.fillRect(i + 1, 0, 0.5, dataValue[i]);
}
}
function calcY(value) {
y = can.height - value * yScalar;
}
function clearGraph() {
var canvas = document.getElementById("can");
var ctx = canvas.getContext("2d");
// Will always clear the right space
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
</script>
You are making many changes to the context such as scale and translate.
These will affect clearRect() as well. I would suggest in this case to use the save() and restore() methods. save() stores the current state of the context incl. transforms:
ctx = can.getContext("2d");
ctx.save(); // after obtaining the context
... rest of code ...
ctx.restore(); // before clearing
ctx.clearRect(0, 0, can.width, can.height);
Now you can see it works as intended:
Modified fiddle
Of course, clearing right after drawing everything is probably not the real intention but to show that it do work (when you eventually need it - put it first in the code if you intend to redraw the graph).