I'm plotting a graph on a canvas and having trouble to draw the grid of the plot underneath the graph. My data points are drawn as rectangles (fillRect). When I first draw the graph and then draw the grid it works as expected, but since the grid is on the graph it doesnt look good. But when I draw the grid first and then plot the graph, all the grids disappear underneath.
I draw my plots as follows:
var plots = document.getElementsByClassName("PlotCanvas");
for (var x=0; x < tokens.length; x++)
{
var canvas = plots[x];
canvas.width = arrayOfArrays[x].length;
var context = canvas.getContext("2d");
for(var point=1; point<arrayOfArrays[x].length; point++)
{
context.fillRect(point, arrayOfArrays[x][point],...);
}
}
Then draw the grids as:
function DrawGrids(plots)
{
for(var count=0; count<plots.length; count++)
{
var ctx = plots[count].getContext("2d");
ctx.beginPath();
for (var x = 0.5; x < plots[count].width; x += 20) {
ctx.moveTo(x, 0);
ctx.lineTo(x, plots[count].height);
}
for (var y = 0.5; y < plots[count].height; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(plots[count].width, y);
}
ctx.strokeStyle = "#eee";
ctx.stroke();
}
}
Could someone suggest me how I can draw the grid underneath the plot. Or how to draw the graph such that it doesn't draw on the whole canvas thus disappearing the grid drawn earlier.
Thank you.
Use ctx.globalCompositeOperation="destination-over" to draw your grid behind your plots!
// draw your plots here
// save the context
ctx.save();
// set compositing to "destination-over"
// New drawings are drawn behind the existing canvas content.
ctx.globalCompositeOperation = "destination-over";
// draw your grids behind your plots!
DrawGrids();
// restore the context
ctx.restore();
Related
I'm using ChartJS to draw a line chart. The problem is that I'm trying to draw some small lines on Vertical axis to mark the axis in each 10% like this:
extendChartLine: function() {
window.Chart.plugins.register({
beforeDraw: function(chart) {
var ctx = chart.chart.ctx,
chartArea = chart.chartArea,
spacing = (chartArea.bottom - 40) /10;
for(var i = 1; i < 10; i++) {
ctx.restore();
ctx.beginPath();
ctx.moveTo(40, chartArea.bottom - i*spacing);
ctx.strokeStyle = '#333';
ctx.lineTo(47, chartArea.bottom - i*spacing);
ctx.lineWidth = 0.2;
ctx.stroke();
ctx.closePath();
ctx.save();
}
}
});
The problem is that is function is called many times causing those lines are drawing more than one time and collapsing on each others (I know that because some lines are darker than others)
I tried to use a flag variable in order to make use these codes are executing on 1 time only, but no lines are drawing on the chart.
I faced the same, if the charts have the animation option on, I think that is causing the redraw to be called multiple times
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;
}
}
I have two canvas elements and need them to be resized on buttons click.
<div class="sDetails"><div>
<div id="canvasDiv" style="width: 310px;"><canvas id="canvasGraph"></canvas></div></div>
<div class="kDetails"><div><div>
<div id="canvasDiv" style="width: 310px; height: 240px;"><canvas id="canvasGraph"></canvas></div></div>
and the script:
var sketch;var sketch_sl;var onPaint;var canvas=null;var ctx=null;var tmp_ctx=null;
function drawCanvas(div) {
canvas = document.querySelector(div + " #canvasGraph");
ctx = canvas.getContext('2d');
sketch = document.querySelector(div + " #canvasDiv");
sketch_sl = getComputedStyle(sketch);
canvas.width = parseInt(sketch_style.getPropertyValue('width'));
canvas.height = parseInt(sketch_style.getPropertyValue('height'));
tmp_canvas = document.createElement('canvas');
tmp_ctx = tmp_canvas.getContext('2d');
tmp_canvas.id = 'tmp_canvas';
tmp_canvas.width = canvas.width;
tmp_canvas.height = canvas.height;
sketch.appendChild(tmp_canvas);
the redraw function:
// here I must redraw my lines resized 2 times ( *cScale ) where cScale=2 or =1
function drawScales(ctx, canvas)
ctx.strokeStyle = 'green';
ctx.fillStyle = 'green';
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(0, canvas.height);
scaleStep = 24*cScale;
for some reason it works really bad, old positions stay.
Is there a way to completely delete the whole canvas and append it or redraw it completely?
I tried canvas.width=canvas.width, tried ctx.clearRect(0, 0, canvas.width, canvas.height);tmp_ctx.clearRect(0, 0, canvas.width, canvas.height);, tried $(".sDetails #canvasGraph")[0].reset();
logically, drawCanvas(".sDetails");drawLines(ctx, canvas); should redraw it from scratch but it will not.
Resize the canvas element's width & height and use context.scale to redraw the original drawings at their newly scaled size.
Resizing the canvas element will automatically clear all drawings off the canvas.
Resizing will also automatically reset all context properties back to their default values.
Using context.scale is useful because then the canvas will automatically rescale the original drawings to fit on the newly sized canvas.
Important: Canvas will not automatically redraw the original drawings...you must re-issue the original drawing commands.
Illustration with 2 canvases at same size (their sizes are controlled by range controls)
Illustration with left canvas resized larger
Illustration with right canvas resized larger
Here's example code and a Demo. This demo uses range elements to control the resizing, but you can also do the resizing+redrawing inside window.onresize
var canvas1=document.getElementById("canvas1");
var ctx1=canvas1.getContext("2d");
var canvas2=document.getElementById("canvas2");
var ctx2=canvas2.getContext("2d");
var originalWidth=canvas1.width;
var originalHeight=canvas1.height;
var scale1=1;
var scale2=1;
$myslider1=$('#myslider1');
$myslider1.attr({min:50,max:200}).val(100);
$myslider1.on('input change',function(){
var scale=parseInt($(this).val())/100;
scale1=scale;
redraw(ctx1,scale);
});
$myslider2=$('#myslider2');
$myslider2.attr({min:50,max:200}).val(100);
$myslider2.on('input change',function(){
var scale=parseInt($(this).val())/100;
scale2=scale;
redraw(ctx2,scale);
});
draw(ctx1);
draw(ctx2);
function redraw(ctx,scale){
// Resizing the canvas will clear all drawings off the canvas
// Resizing will also automatically clear the context
// of all its current values and set default context values
ctx.canvas.width=originalWidth*scale;
ctx.canvas.height=originalHeight*scale;
// context.scale will scale the original drawings to fit on
// the newly resized canvas
ctx.scale(scale,scale);
draw(ctx);
// always clean up! Reverse the scale
ctx.scale(-scale,-scale);
}
function draw(ctx){
// note: context.scale causes canvas to do all the rescaling
// math for us, so we can always just draw using the
// original sizes and x,y coordinates
ctx.beginPath();
ctx.moveTo(150,50);
ctx.lineTo(250,150);
ctx.lineTo(50,150);
ctx.closePath();
ctx.stroke();
ctx.fillStyle='skyblue';
ctx.beginPath();
ctx.arc(150,50,20,0,Math.PI*2);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(250,150,20,0,Math.PI*2);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();;
ctx.arc(50,150,20,0,Math.PI*2);
ctx.fill();
ctx.stroke();
}
$("#canvas1, #canvas2").mousemove(function(e){handleMouseMove(e);});
var $mouse=$('#mouse');
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
var bb=e.target.getBoundingClientRect();
mouseX=parseInt(e.clientX-bb.left);
mouseY=parseInt(e.clientY-bb.top);
if(e.target.id=='canvas1'){
$mouse.text('Mouse1: '+mouseX/scale1+' / '+mouseY/scale1+' (scale:'+scale1+')');
}else{
$mouse.text('Mouse2: '+mouseX/scale2+' / '+mouseY/scale2+' (scale:'+scale2+')');
}
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div>Resize left canvas</div>
<input id=myslider1 type=range><br>
<div>Resize right canvas</div>
<input id=myslider2 type=range><br>
<h4 id=mouse>Mouse coordinates:</h4>
<canvas id="canvas1" width=300 height=300></canvas>
<canvas id="canvas2" width=300 height=300></canvas>
If you need scale-independent positions you could use normalized values ([0, 1]) instead and use the size of canvas as the scale factor. This way you can scale and store values without too much concern about the actual target size.
You would also be able to use the mouse positions almost as is and normalize by just dividing them on canvas size.
For example:
When rendering, a point of (1,1) will always draw in lower-right corner as you would do (1 * canvas.width, 1 * canvas.height).
When you store a point you would use the mouse position and divide it on the canvas dimension, for example, if I click in the lower right corner of a canvas of size 400x200, the points would be 400/400 = 1, 200/200 = 1.
Note that width and height would be exclusive (ie. width-1 etc.), but for sake of simplicity...
Example
In this example you can start with any size of the canvas, draw points which are normalized, change size of canvas and have the points redrawn proportionally relative to the original position.
var rng = document.querySelector("input"),
c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
points = [];
// change canvas size and redraw all points
rng.onchange = function() {
c.width = +this.value;
render();
};
// add a new normalized point to array
c.onclick = function(e) {
var r = this.getBoundingClientRect(), // to adjust mouse position
x = e.clientX - r.left,
y = e.clientY - r.top;
points.push({
x: x / c.width, // normalize value to range [0, 1]
y: y / c.height
}); // store point
render(); // redraw (for demo)
};
function render() {
ctx.clearRect(0, 0, c.width, c.height); // clear canvas
ctx.beginPath(); // clear path
for(var i = 0, p; p = points[i]; i++) { // draw points as fixed-size circles
var x = p.x * c.width, // normalized to absolute values
y = p.y * c.height;
ctx.moveTo(x + 5, y);
ctx.arc(x, y, 5, 0, 6.28);
ctx.closePath();
}
ctx.stroke();
}
canvas {background:#ddd}
<h3>Click on canvas to add points, then resize</h3>
<label>Width: <input type="range" min=50 max=600 value=300></label><br>
<canvas></canvas>
I decided to use a scale variable to resize my scales. I resize the canvas canvas.width *= 2; and then I redraw my scales.
var scaleStep;
and use add it into the code: ctx.lineTo(12*24*cScale+12, canvas.height-24); where the scaling needs to be done.
The scaleStep is 2 when maximizing the canvas and 1 when returning to the original size.
I am trying to draw lines with canvas and I am changing the coordinates with a for loop.
here is my canvas element:
<canvas id="c" width="300px" height="300px"></canvas>
and here is the js codes:
var c = document.getElementById('c');
ci = c.getContext('2d');
for(var a = 18; a < 300; a +=18){
fnc(a, ci);
}
function fnc(x, ci){
ci.strokeStyle = 'red';
ci.moveTo(0, x);
ci.lineTo(300, x); ci.lineWidth = 0.2; ci.stroke();
}
As you can see I am trying to draw these lines with 18px spaces between them. But the thickness of the lines and the color(or opacity, I am not sure) are changing from top to bottom.
Here is a fiddle : http://jsfiddle.net/J6zzD/1/
So what is wrong with that I can't find my mistake. Why are the color and the thicknesses are different?
UPDATE :
I just wrote these lines out of the function and now all the lines becomes faded but thicknesses are same. So strange :
ci.strokeStyle = 'red';
ci.lineWidth = 0.2; ci.stroke();
here is demo : http://jsfiddle.net/J6zzD/4/
That's again the eternal issue of forgetting to call beginPath.
Each time you call moveTo then lineTo, you create a new *sub*path, which adds to the current Path.
Then each time you call stroke(), the current path, so all the current subpaths get re-drawn, when the last added path is drawn for the first time.
Since opacities will add-up, top lines will reach 100% opacity (alpha=255) when the bottom line, drawn once, will have a 20% opacity (lineWidth=0.2) .
In your second fiddle, you stroke only once, so all lines have 20% opacity, which is correct for the 0.2 lineWidth.
So : use beginPath before drawing a new figure.
In this case you have two choices :
• draw line by line
OR
• draw once a path with all lines as subpath.
(see code below).
TIP : To get clean lines remember that pixels's center is at the (+0.5, +0.5) coordinates of each pixels, so
a 'trick' is to translate by 0.5, 0.5 on app start, then only use rounded coordinates and lineWidth.
1) draw line by line
http://jsfiddle.net/gamealchemist/J6zzD/6/
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.translate(0.5, 0.5);
ctx.lineWidth = 1;
for (var y = 18; y < 300; y += 18) {
strokeLine(ctx, y);
}
function strokeLine(ctx, y) {
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.moveTo(0, y);
ctx.lineTo(300, y);
ctx.stroke();
}
2) draw multiple subPath :
(you can have only one color for one stroke() )
http://jsfiddle.net/gamealchemist/J6zzD/7/
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.translate(0.5, 0.5);
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.beginPath();
for (var y = 18; y < 300; y += 18) {
addLineSubPath(ctx, y);
}
ctx.stroke();
function addLineSubPath(ctx, y) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
}
See: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Applying_styles_and_colors#A_lineWidth_example
Because canvas coordinates do not directly reference pixels, special care must be taken to obtain crisp horizontal and vertical lines.
Basically, because you're trying to draw a line that's 0.2 pixels wide, the browser does some math to approximate a continuous number into discrete units and you get your "fading" lines.
So now we can fix up your code by changing context.lineWidth to 1 (I actually remove it because it defaults to 1) and shifting everything down by half a pixel.
var c = document.getElementById('c');
ci = c.getContext('2d');
for(var a = 18.5; a < 300.5; a +=18)
{
fnc(a, ci);
}
function fnc(x, ci)
{
ci.strokeStyle = 'red';
ci.moveTo(0, x);
ci.lineTo(300, x);
ci.stroke();
}
Demo
How can I fill the path that I've drawn in red?
http://jsfiddle.net/MVXZu/1/
I've tried to use fill but it doesn't fill my path as I want it to - that is to fill in the red outline - but instead it fills only a diagonal portion (comment out the ctx.fill(); to see the full outline I want to fill) The code that's drawing the line is this:
//loop through the data
ctx.beginPath();
for (var i = 0; i < data.length; i++) {
ctx.lineWidth=3;
ctx.lineCap = 'round';
ctx.strokeStyle = 'red';
ctx.moveTo(linePosX,linePosY);
ctx.lineTo((i*cellWidth) + cellWidth + padding,(tableHeight + padding) - data[i].v);
linePosX = i*cellWidth + padding + cellWidth;
linePosY = (tableHeight + padding) - data[i].v;
if(i == 13) {
ctx.lineTo(linePosX,tableHeight+padding);
ctx.lineTo(padding,tableHeight+padding);
}
ctx.fillStyle="red";
ctx.fill();
ctx.stroke();
ctx.closePath();
}
Don't put the beginPath into the loop
Don't use moveTo, as that creates a new polygon to fill (see it here, as the result of closePath()). You're at the correct position after the before lineTo anyway. Without it it works better…
Here's the fixed fiddle: http://jsfiddle.net/MVXZu/3/
Pseudo-code:
ctx.beginPath();
// set ctx styles
ctx.moveTo( bottom-left corner );
for each (point in data) {
ctx.lineTo(point);
}
ctx.lineTo( bottom-right corner );
ctx.closePath(); // automatically moves back to bottom left corner
ctx.fill();
Hopefully this is what you were wanting: http://jsfiddle.net/MVXZu/2/
Because you were only running some of the code if (i == 13) {}, only the moveTo and the first lineTo were getting called. This was resulting in a line at the top of the section, but not a box to fill.
I moved a lot of the drawing outside of the loop. It creates the first point in the bottom left of the graph, then the points along the top, then the point in the bottom right. Then it fills in the whole graph after the path is finished.