I´ve been trying to make a desktop app (javascript, canvas) and draw 413.280 clickable circles in a certain pattern, but I can´t really figure out how to do it. I´m not convinced canvas is the best solution but I dont know how to solve this and get an app with a reasonable performance.
Here´s the layout I´m trying to get:
circle layout
I want 2 rows of circles within each line. the division in the middle is to be left empty.
Every left row has to be 588 circles.
Every right row has to be 560 circles
There are 180 lines on each side which means there's (588*2*180)= 211680 circles on the left side.
There's (560*2*180)=201600 circles on the right side.
can anyone point me in the right direction, maybe have a clue how I can solve this in the most efficient way possible? Thanks in advance.
EDIT: here's the JSFiddle I've got so far jsfiddle.net/cmxLoqej/2/
JavaScript
window.onload = draw;
function draw() {
var canvas = document.getElementById('canvas');
var c = canvas.getContext('2d');
var ycoordinate = 20;
//draw the line 180 times
for (var x = 1; x <= 180; x++) {
// draw the left side
for (var i = 1; i <= 1; i++){
c.strokeStyle = 'black';
c.moveTo(0,ycoordinate);
c.lineTo(6468,ycoordinate);
c.stroke();
ycoordinate = ycoordinate + 40;
}
}
var ycoordinate = 20;
//draw right side
for (var x = 1; x <= 180; x++) {
for (var j = 1; j <= 1; j++){
c.strokeStyle = 'black';
c.moveTo(6776,ycoordinate);
c.lineTo(canvas.width,ycoordinate);
c.stroke();
ycoordinate = ycoordinate + 40;
}
}
}
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var canvasPattern = document.createElement("canvas");
canvasPattern.width=11;
canvasPattern.height=20;
var contextPattern = canvasPattern.getContext("2d");
contextPattern.beginPath();
contextPattern.arc(5, 10, 5, 0, 2 * Math.PI, false);
contextPattern.strokeStyle = '#003300';
contextPattern.stroke();
var pattern = context.createPattern(canvasPattern,"repeat");
context.fillStyle = pattern;
context.fillRect(0, 20, 6468, 7160);
context.fill();
var canvas2 = document.getElementById('canvas');
var context2 = canvas.getContext('2d');
var canvasPattern2 = document.createElement("canvas");
canvasPattern2.width=11;
canvasPattern2.height=20;
var contextPattern2 = canvasPattern.getContext("2d");
contextPattern2.beginPath();
contextPattern2.arc(5, 10, 5, 0, 2 * Math.PI, false);
contextPattern2.strokeStyle = '#003300';
contextPattern2.stroke();
var pattern2 = context2.createPattern(canvasPattern2,"repeat");
context2.fillStyle = pattern;
context2.fillRect(6776, 20, 6160, 7160);
context2.fill();
HTML
<!DOCTYPE html>
<html>
<body>
<canvas {
id="canvas";
width= "12936" ;
height ="7400";
style= "border: 1px solid black;";
padding: 0;
margin: auto;
display: block;
}>
</canvas>
</body>
</html>
Use fill patterns of circles to create rectangular canvas images of
a single row of the left hand side
a single row of the right hand side
a combined row of each side
a single canvas of 180 rows
Use temporary CANVAS objects along the way as necessary to use the context2D.createPattern method. You should not need to add them to the DOM just to manipulate pixels.
Modify the algorithm if needed as you learn. Happy coding!
Update (edit)
Running the code added to the question shows all circles being evenly spaced horizontally and vertically.
A simpler way of drawing the canvas may be to fill two rectangles that exactly cover the left and right areas of the canvas with the circle pattern, and draw the grid lines on the canvas afterwards instead of before.
Finding the circle clicked
A click event listener on the canvas is passed a mouse event object.
The classical way to determine which circle was clicked was to first perform arithmetic on the screenX and screenY event properties for screen position, window.scrollX and window.scrollY for document scroll amounts, and the position of the canvas within the document, to find where the click occured in the canvas.
Although not yet fully standardized, offsetX and offsetY properties of the mouse event object provide the result directly. The MDN reference shows fairly good cross browser support.
Then knowledge of canvas layout can be used to determine which rectangular circle pattern was clicked, and with a bit of algebra if the click is inside the circle.
Related
How is it / is it possible to draw using the mouse a canvas using 3 axis(x,y,z).
I know that one can draw a canvas on 2 axis and I have done that successfully.
But I have no idea of how I shall draw it on 3 axis (for example a cube).
Following shows some 2d canvas drawing functionallity
$(canvas).on('mousemove', function(e) {
mousex = parseInt(e.clientX-canvasx);
mousey = parseInt(e.clientY-canvasy);
if(mousedown) {
ctx.beginPath();
if(tooltype=='draw') {
ctx.globalCompositeOperation = 'source-over';
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
} else {
ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 10;
}
ctx.moveTo(last_mousex,last_mousey);
ctx.lineTo(mousex,mousey);
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
}
last_mousex = mousex;
last_mousey = mousey;
//Output
$('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});
The full code https://jsfiddle.net/ArtBIT/kneDX/.
But how can I add a z axis and draw a 3d canvas for instance a cube.
With 2D it is simple, you have the X and Y coordinate of the mouse, and when a mouse button is clicked you can change pixels at that location in the canvas.
3D on the other hand is quite hard. Because of the extra dimension that does not exist on the 2D surface, you need to know how to control the 3D positions. And to make matters worse, with that third dimension comes all kinds of extra's that everyone likes to have: lightning and shadows, effects, focus, etc.
Simple drawing
In its most basic form, (set aside some arithmic) you can flatten the Z axis on the 2D surface with a single division. Suppose that you have a point in 3D which consists of three points on three axis (x3d, y3d, z3d) then you can do:
var x2d = x3d / z3d;
var y2d = y3d / z3d;
If you're new to 3D, you will want to play with this first. Here is a tutorial.
Advanced drawing
For just particles and lines this is rather straightforward, although you might want to use another perspective. But it gets more complicated soon when you use objects and want to rotate them in 3D space. This is why most people rely on an engine like three.js to do the 3D drawing for them.
Control 3D space
When drawing with the mouse, you need to map the 2D mouse movement to 3D for control. For examples, have a look a these 3D GUI's: Microsoft's Paint 3D, Google's Sketchup, and Blender. Note that the more kinds of mappings needs to be implemented (like scaling and other transformations) the more math is required.
Using three.js would help you out. See here: https://jsfiddle.net/bn890dtc/
The core code for drawing the line as your click and drag:
function onMouseMove(evt) {
if (renderer) {
var x = (event.clientX / window.innerWidth) * 2 - 1;
var y = -(event.clientY / window.innerHeight) * 2 + 1;
var z = 0
var vNow = new THREE.Vector3(x, y, z);
vNow.unproject(camera);
splineArray.push(vNow);
}
}
The line
vNow.unproject(camera);
will project your drawing into 3D space.
This function will update the line in 3D space:
function updatePositions() {
var positions = line.geometry.attributes.position.array;
var index = 0;
for ( var i = 0; i < splineArray.length; i ++ ) {
positions[ index ++ ] = splineArray[i].x;
positions[ index ++ ] = splineArray[i].y;
positions[ index ++ ] = splineArray[i].z;
}
}
Concerns to be addressed:
looks like the straight line on the left is higher than the one on the right. I don't want that. Why is that happening and how could I fix it?
Are all the lines(recs) the same length. not sure but it looks like the ones that go outwards at 0.7853981633974483 radians are smaller
window.onload = function(){
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.translate(200,200);
for(var i = 0; i < 5; i++){
context.save()
context.rotate(Math.PI / 4 * i);
context.fillStyle = "red";
context.fillRect(0,0,70,3 )
context.restore()
}
}
<canvas id="canvas" width="400" height="400"></canvas>
EDIT: Also I want to ask you how you would go about labeling these slices that make up the angle. for example slice 1 gets "1" and so on 1 2 3 4 5. it should be positioned by the vertex(angle)
Firstly, the lengths are all the same. If the appear different it is likely due to an illusion from the slight overlapping at the origin.
Now the reason why things aren't lining up is due to the nature of rotation and rectangles. When you rotate a rect the rotational origin is at the top left corner of the rectangle. So when rotated 180 degrees, the origin of the rectangle will be the bottom right. This can be seen more obviously if you widen your rectangles, and change their colours. For example:
var cols = ['red', 'green', 'blue', 'yellow', 'purple'];
window.onload = function(){
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.translate(200,200);
for(var i = 0; i < 5; i++){
context.save();
context.rotate(Math.PI / 4 * i);
context.fillStyle = cols[i];
context.fillRect(0,0,70,30);
context.restore();
}
}
<canvas id="canvas" width="400" height="400"></canvas>
Sow how do you fix this? One way to fix is to translate each rect on the y axis, half of its width after rotation. For example:
var cols = ['red', 'green', 'blue', 'black', 'purple'];
// the line width (technically rect height)
var width = 3;
var length = 70;
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.translate(200,200);
for(var i = 0; i < 5; i++){
var rotAmount = Math.PI / 4 * i;
context.save();
context.rotate(rotAmount);
context.translate(0, -(width / 2));
context.fillStyle = cols[i];
context.fillRect(0,0,length,width);
context.translate(length + 20, 0);
context.rotate(-rotAmount);
context.font="18px Verdana";
context.fillText(i+1,-5,5);
context.restore();
}
<canvas id="canvas" width="400" height="400"></canvas>
Also added fillText as per #markE's comment, to get you started with drawing the numbers.
So how does this work? Basically after drawing the line, you move the axis to where you want each number to be (translate), and then rotate the axis in reverse, the same amount that you originally rotated the line (rotate). This will rotate the numbers to their original coordinate system.
I'm trying to draw a line of squares by using a for loop.
When I say line I mean that I want one square standing next to other one but with a space between them.
I tried the next code -
The html part -
<canvas id="myCan" height="300px" width="300px"></canvas>
and the script part -
$(document).ready(function () {
var canvas = document.getElementById("myCan");
var ctx = canvas.getContext("2d");
var positionx = 50;
var positiony = 50;
var i = 0;
for (i; i < 20; i++) {
ctx.fillStyle = "rgb(255, 0, 0)";
ctx.fillRect(positionx, positiony, 50, 50);
positionx += 2;
}
});
The thing is that I'm getting a simple Line- meaning there's no space between the squares.
How can i fix it?
Thanks for any kind of help
Change positionx += 2; to positionx += 52;
At the moment what you tell the browser is: draw this shape 50 px wide, then move 2 px to the right and draw the next shape 50 px wide.
What you want is: draw this shape 50 px wide, then move 50px+X to the right and draw the next shape.
Working fiddle:
http://jsfiddle.net/34zcda9q/
I am having a problem with randomising rectangle locations on the screen. I have a 5 x 5 grid set up with in html and they are all formatted properly. I am then drawing bars into this grid which also works fine. The problem is that when I want to randomly jitter these objects they sometimes move off the canvas (to the left, or to the top only) apparently. I am trying to reposition the canvas via jQuery with no success.
Also, I have the feeling my canvas sizes are wrong but if I set the sizes to 100% in the CSS section, the bars are suddenly very small and I have no idea why.
Here is the interesting piece of the code. the problem is the "jitter" part
function drawStimulus(size, color, orientation, location){
// size for each box, refers to the first box in the first row
// box size should be around 40 on a 22inch screen in this jsfiddle
var box_size = $("#testbox").height();
var stimulus_size = box_size * size; // size is either 1 (large) or 2/3
var size_diff = box_size-stimulus_size;
var c = document.getElementById(location);
c.width = box_size;
c.height = box_size;
// if a perfect grid is not wanted, jitter bars randomly
// by a maximum of 1/3 of the box size in any direction
if(jitter){
var hjitter = rand(-box_size/3, box_size/3);
var vjitter = rand(-box_size/3, box_size/3);
c.style.left = String(c.style.left+hjitter)+"px";
c.style.top = String(c.style.top+vjitter)+"px";
}
var ctx = c.getContext("2d");
// rotate around center
ctx.translate(box_size/2, box_size/2);
ctx.rotate(orientation*Math.PI / 180);
ctx.translate(-box_size/2, -box_size/2);
// draw bars
ctx.beginPath();
ctx.strokeStyle = color;
ctx.moveTo(c.offsetLeft+size_diff/2,c.offsetTop+box_size/2);
ctx.lineTo(c.offsetLeft+stimulus_size+size_diff/2,c.offsetTop+box_size/2);
ctx.lineWidth = size*10;
ctx.closePath();
ctx.stroke();
}
Here is also a jsfiddle with the full code: http://jsfiddle.net/msauter/pdrwwm7x/
I am drawing onto an HTML5 canvas with stroke() and regardless of how or when I set globalAlpha, the stroke is being drawn with some measure of transparency. I'd like for the stroke to be completely opaque (globalAlpha=1). Is there somewhere else where the alpha is being set?
In this jsfiddle, I am drawing a grid of solid black lines onto a canvas. For me, the result shows dots at the intersections, confirming that the lines are partially transparent. Here's the gist of it:
context.globalAlpha=1;
context.strokeStyle="#000";
context.beginPath();
/* draw the grid */
context.stroke();
context.closePath;
The especially weird thing (to me) is that this problem was not occurring in my code before my last computer restart, so I'm guessing there was something hanging around in the cache that was keeping the alpha at my desired level.
I'm obviously missing something here... thanks for any help you can provide.
Real answer :
Each point in a canvas has its center in its (+0.5, +0.5) coordinate.
So to avoid artifacts, start by translating the context by (0.5, 0.5) ,
then round the coordinates.
css scaling creates artifact, deal only with canvas width and height, unless
you want to deal with hidpi devices with webGL, or render at a lower resolution
with both webGL and context2D.
-> in your case, your setup code would be (with NO css width/height set ) :
( http://jsfiddle.net/gamealchemist/x9bTX/8/ )
// parameters
var canvasHorizontalRatio = 0.9;
var canvasHeight = 300;
var hCenterCanvas = true;
// setup
var canvasWidth = Math.floor(window.innerWidth * canvasHorizontalRatio);
var cnv = document.getElementById("myCanvas");
cnv.width = canvasWidth;
cnv.height = canvasHeight;
if (hCenterCanvas)
cnv.style['margin-left'] = Math.floor((window.innerWidth - canvasWidth) * 0.5) + 'px';
var ctx = cnv.getContext("2d");
ctx.translate(0.5, 0.5);
gridContext();
The rest of the code is the same as your original code, i just changed the size of you squares to get quite the same visual aspect.
ctx.beginPath();
for (var i=60; i<canvasHeight; i+=60) {
ctx.moveTo(0,i);
ctx.lineTo(canvasWidth,i);
}
for (i=60; i<canvasWidth; i+=60) {
ctx.moveTo(i,0);
ctx.lineTo(i,canvasHeight);
}
ctx.strokeStyle="#000";
ctx.stroke();
ctx.closePath();
With those changes we go from :
to :
Edit : to ensure rounding, in fact i think most convenient is to inject the context and change moveTo, lineTo :
function gridContext() {
var oldMoveTo = CanvasRenderingContext2D.prototype.moveTo;
CanvasRenderingContext2D.prototype.moveTo = function (x,y) {
x |= 0; y |= 0;
oldMoveTo.call(this, x, y);
}
var oldLineTo = CanvasRenderingContext2D.prototype.lineTo;
CanvasRenderingContext2D.prototype.lineTo = function (x,y) {
x |= 0; y |= 0;
oldLineTo.call(this, x, y);
}
}
Obviously, you must do this for all drawing functions you need.
When drawing lines on a canvas, the line itself is exactly on the pixel grid. But because the line is one pixel wide, half of it appears in each of the pixels to either side of the grid, resulting in antialising and a line that is basically 50% transparent over two pixels.
Instead, offset your line by 0.5 pixels. This will cause it to appear exactly within the pixel.
Demo