I'm using the wPaint plugin and I am attempting to add a few more features. What I need is a drawn line to end with an "arrowhead". I have tried just about everything I could think of, but I can only get half of the arrow ( imagine <-----, but the head only extends to the bottom or the top, but never both directions.)
Here is the function for drawing the line (with the half arrowhead):
drawArrowMove: function(e, _self)
{
var xo = _self.canvasTempLeftOriginal;
var yo = _self.canvasTempTopOriginal;
if(e.pageX < xo) { e.x = e.x + e.w; e.w = e.w * -1}
if(e.pageY < yo) { e.y = e.y + e.h; e.h = e.h * -1}
_self.ctxTemp.lineJoin = "round";
_self.ctxTemp.beginPath();
_self.ctxTemp.moveTo(e.x, e.y);
_self.ctxTemp.lineTo(e.x + e.w, e.y + e.h);
_self.ctxTemp.closePath();
_self.ctxTemp.moveTo(e.x, e.y);
_self.ctxTemp.lineTo(15,10);
_self.ctxTemp.stroke();
}
Any help/ideas/tips would be helpful.
Thanks.
This is how to create a line object that draws arrowheads on both ends
The interesting part is calculating the angle of the arrowheads like this:
var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
startRadians+=((this.x2>=this.x1)?-90:90)*Math.PI/180;
var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
endRadians+=((this.x2>=this.x1)?90:-90)*Math.PI/180;
The rest is just drawing the line and 2 triangles for arrowheads the calculated rotations
Line.prototype.drawArrowhead=function(ctx,x,y,radians){
ctx.save();
ctx.beginPath();
ctx.translate(x,y);
ctx.rotate(radians);
ctx.moveTo(0,0);
ctx.lineTo(5,20);
ctx.lineTo(-5,20);
ctx.closePath();
ctx.restore();
ctx.fill();
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/Sg7EZ/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
function Line(x1,y1,x2,y2){
this.x1=x1;
this.y1=y1;
this.x2=x2;
this.y2=y2;
}
Line.prototype.drawWithArrowheads=function(ctx){
// arbitrary styling
ctx.strokeStyle="blue";
ctx.fillStyle="blue";
ctx.lineWidth=1;
// draw the line
ctx.beginPath();
ctx.moveTo(this.x1,this.y1);
ctx.lineTo(this.x2,this.y2);
ctx.stroke();
// draw the starting arrowhead
var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
startRadians+=((this.x2>this.x1)?-90:90)*Math.PI/180;
this.drawArrowhead(ctx,this.x1,this.y1,startRadians);
// draw the ending arrowhead
var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
endRadians+=((this.x2>this.x1)?90:-90)*Math.PI/180;
this.drawArrowhead(ctx,this.x2,this.y2,endRadians);
}
Line.prototype.drawArrowhead=function(ctx,x,y,radians){
ctx.save();
ctx.beginPath();
ctx.translate(x,y);
ctx.rotate(radians);
ctx.moveTo(0,0);
ctx.lineTo(5,20);
ctx.lineTo(-5,20);
ctx.closePath();
ctx.restore();
ctx.fill();
}
// create a new line object
var line=new Line(50,50,150,150);
// draw the line
line.drawWithArrowheads(context);
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
This goes wrong for vertical lines.
Try
var line=new Line(50,50,50,275)
As addition to markE's answer combined with user1707810 comment:
Both blocks of (start/end radians):
- ((this.x2 > this.x1)?-90:90)*Math.PI/180;
should be changed to :
- ((this.x2 >= this.x1)?-90:90)*Math.PI/180;
Simpler version
key difference. Using Math.atan2 removes the need for if
This one also puts the arrowheads at the ends of the line rather than past the end of the line
In other words
this
start end
|<------->|
vs this
<|---------|>
function arrow(ctx, x1, y1, x2, y2, start, end) {
var rot = -Math.atan2(x1 - x2, y1 - y2);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
if (start) {
arrowHead(x1, y1, rot);
}
if (end) {
arrowHead(x2, y2, rot + Math.PI);
}
}
function arrowHead(x, y, rot) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rot);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(-5, -12);
ctx.lineTo(5, -12);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// test it -------
var ctx = document.createElement("canvas").getContext("2d");
document.body.appendChild(ctx.canvas);
// draw some arrows
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
for (var ii = 0; ii <= 12; ++ii) {
var u = ii / 12;
var color = hsl(u * 360, 1, 0.5);
ctx.fillStyle = color;
ctx.strokeStyle = color;
var a = u * Math.PI;
var x = Math.cos(a) * 120;
var y = Math.sin(a) * 70;
arrow(ctx, -x, -y, x, y, true, true);
// draw the end points to see the arrowheads match
ctx.fillStyle = "#000";
ctx.fillRect(-x - 1, -y - 1, 3, 3);
ctx.fillRect( x - 1, y - 1, 3, 3);
}
ctx.restore();
function hsl(h, s, l) {
return `hsl(${h},${s * 100}%,${l * 100}%)`;
}
canvas { border: 1px solid black; }
One problem with the above solution is if you thicken the line stroke it would poke through the arrowhead. It's not hard to fix but you'd have to compute the length of the line in pixels, then subtract the size of the arrowhead from both sides.
something like this
function arrow(ctx, x1, y1, x2, y2, start, end) {
var dx = x2 - x1;
var dy = y2 - y1;
var rot = -Math.atan2(dx, dy);
var len = Math.sqrt(dx * dx + dy * dy);
var arrowHeadLen = 10;
ctx.save();
ctx.translate(x1, y1);
ctx.rotate(rot);
ctx.beginPath();
ctx.moveTo(0, start ? arrowHeadLen : 0);
ctx.lineTo(0, len - (end ? arrowHeadLen : 0));
ctx.stroke();
if (end) {
ctx.save();
ctx.translate(0, len);
arrowHead(ctx);
ctx.restore();
}
if (start) {
ctx.rotate(Math.PI);
arrowHead(ctx);
}
ctx.restore();
}
function arrowHead(ctx) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(-5, -12);
ctx.lineTo(5, -12);
ctx.closePath();
ctx.fill();
}
// test it -------
var ctx = document.createElement("canvas").getContext("2d");
document.body.appendChild(ctx.canvas);
// draw some arrows
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
for (var ii = 0; ii < 12; ++ii) {
var u = ii / 12;
var color = hsl(u * 360, 1, 0.5);
ctx.fillStyle = color;
ctx.strokeStyle = color;
var a = u * Math.PI;
var x = Math.cos(a) * 120;
var y = Math.sin(a) * 70;
arrow(ctx, -x, -y, x, y, true, true);
ctx.fillStyle = "#000"; // mark the ends so we can see where they are
ctx.fillRect(-x - 1, -y - 1, 3, 3);
ctx.fillRect( x - 1, y - 1, 3, 3);
}
ctx.restore();
function hsl(h, s, l) {
return `hsl(${h},${s * 100}%,${l * 100}%)`;
}
canvas { border: 1px solid black; }
In other words the first solution draws arrows like this
Where as the second solution draws arrows like this
my simple solution
ctx.beginPath();
ctx.moveTo(ax,ay);
ctx.lineTo(bx,by);
ctx.stroke();
ctx.closePath();
angle=Math.PI+Math.atan2(by-ay,bx-ax);
angle1=angle+Math.PI/6;
angle2=angle-Math.PI/6;
ctx.beginPath();
ctx.moveTo(bx,by);
ctx.arc(bx,by,5,angle1,angle2,true);
ctx.lineTo(bx,by);
ctx.fill();
ctx.closePath();
Related
I order to build a HTML 5 datacenter floor plan, I would like to create a polygon filled with a grid. This grid must not be a picture pattern as I would like to be able to zoom or rotate the floor plan without having pixelization.
I would like to be able to create this kind of output :
How can I do that ?
There are multiple ways, like
using a clipping region
var ctx = c.getContext('2d');
drawShape();
ctx.stroke();
ctx.save(); // so we can remove the clipping
ctx.clip();
drawGrid();
ctx.restore(); // remove the clipping
function drawShape() {
ctx.beginPath();
var pts = [
20, 20,
80, 20,
90, 50,
120, 90,
30, 80,
20,20
];
for(var i=0;i<pts.length;i+=2){
ctx.lineTo(pts[i], pts[i+1]);
}
}
function drawGrid() {
ctx.beginPath();
for(var x=-.5; x<c.width; x+=20) {
ctx.moveTo(x, 0);
ctx.lineTo(x, c.height);
}
for(var y=-.5; y<c.height; y+=20) {
ctx.moveTo(0, y);
ctx.lineTo(c.width, y);
}
ctx.stroke();
}
<canvas id="c"></canvas>
using compositing
var ctx = c.getContext('2d');
drawGrid();
ctx.globalCompositeOperation = 'destination-in';
drawShape();
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
ctx.stroke();
function drawShape() {
ctx.beginPath();
var pts = [
20, 20,
80, 20,
90, 50,
120, 90,
30, 80,
20,20
];
for(var i=0;i<pts.length;i+=2){
ctx.lineTo(pts[i], pts[i+1]);
}
}
function drawGrid() {
ctx.beginPath();
for(var x=-.5; x<c.width; x+=20) {
ctx.moveTo(x, 0);
ctx.lineTo(x, c.height);
}
for(var y=-.5; y<c.height; y+=20) {
ctx.moveTo(0, y);
ctx.lineTo(c.width, y);
}
ctx.stroke();
}
<canvas id="c"></canvas>
But in your case, a regular grid, it might actually be better to use a pattern.
Indeed, you'd have to only draw one cell every time you change the scale of your grid, for translations, this can be done internally.
So I didn't do the performance tests myself, and thus encourage you to double check it's worth it, but theoretically, it might be faster and esaier to manage than redrawing the grid every time.
var ctx = c.getContext('2d');
var pat_ctx = document.createElement('canvas').getContext('2d');
var cell_size = 20;
// just a basic drawing example
// first we generate the grid as a pattern
ctx.fillStyle = generatePattern(cell_size, cell_size);
drawShape();
ctx.stroke();
// we move the pattern by half a cell because we actually drawn only a cross
ctx.translate(-cell_size / 2, -cell_size / 2);
ctx.fill();
// make the grid follow the mouse
// without having to redraw ourself the grid
onmousemove = function(e) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, c.width, c.height);
drawShape();
ctx.stroke();
// move the grid
ctx.translate(e.clientX - cell_size / 2, e.clientY - -cell_size / 2);
ctx.fill();
}
// click to zoom (+shift to zoom out)
onclick = function(e) {
if (e.shiftKey) cell_size--;
else cell_size++;
ctx.fillStyle = generatePattern(cell_size, cell_size);
onmousemove(e);
}
// dimply draws a cross
function generatePattern(w, h) {
var canvas = pat_ctx.canvas;
canvas.width = w;
canvas.height = h;
pat_ctx.moveTo(w / 2, 0);
pat_ctx.lineTo(w / 2, h);
pat_ctx.moveTo(0, h / 2);
pat_ctx.lineTo(w, h / 2);
pat_ctx.stroke();
return pat_ctx.createPattern(canvas, 'repeat');
}
function drawShape() {
ctx.beginPath();
var pts = [
20, 20,
80, 20,
90, 50,
120, 90,
30, 80,
20, 20
];
for (var i = 0; i < pts.length; i += 2) {
ctx.lineTo(pts[i], pts[i + 1]);
}
}
<canvas id="c"></canvas>
Sorry I am new to Canvas and dont know how to google this out. Problem is that I cant draw on mask if previous layer (night sky) is present.
Here are the two snippets:
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const { width: w, height: h } = canvas;
// first layer
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = '#555';
let x, y, radius;
for (let i = 0; i < 550; i++) {
x = Math.random() * w;
y = Math.random() * h;
radius = Math.random() * 3;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
// destination
ctx.font = 'bold 70pt monospace';
ctx.fillStyle = 'black';
ctx.fillText('FOO', 10, 60);
ctx.fillText('BAR', 10, 118);
ctx.fill();
// source
ctx.globalCompositeOperation = 'source-atop';
for (let i = 0; i < 6; i++) {
ctx.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx.fillRect(0, i * 20, 200, 20);
}
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
EXPECTED RESULT (but with the first layer - night sky):
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const { width: w, height: h } = canvas;
// destination
ctx.font = 'bold 70pt monospace';
ctx.fillStyle = 'black';
ctx.fillText('FOO', 10, 60);
ctx.fillText('BAR', 10, 118);
ctx.fill();
// source
ctx.globalCompositeOperation = 'source-atop';
for (let i = 0; i < 6; i++) {
ctx.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx.fillRect(0, i * 20, 200, 20);
}
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
Compositing will affect the whole context.
source-atop mode will draw only where there were existing pixels (i.e only where alpha > 0).
When you draw your background, all the pixels of your context have alpha values set to 1.
This means that source-atop will not produce anything on your fully opaque image.
Once you understand these points, it's clear that you need to make your compositing alone.
It could be e.g on a different off-screen canvas that you would then draw back on the main canvas with ctx.drawImage(canvas, x, y).
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const {
width: w,
height: h
} = canvas;
// background
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = '#555';
let x, y, radius;
for (let i = 0; i < 550; i++) {
x = Math.random() * w;
y = Math.random() * h;
radius = Math.random() * 3;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
// text compositing on an off-screen context
const ctx2 = Object.assign(document.createElement('canvas'), {
width: 200,
height: 120
}).getContext('2d');
// text
ctx2.font = 'bold 70pt monospace';
ctx2.fillStyle = 'black';
ctx2.fillText('FOO', 10, 60);
ctx2.fillText('BAR', 10, 118);
ctx2.globalCompositeOperation = 'source-atop';
// rainbow
for (let i = 0; i < 6; i++) {
ctx2.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx2.fillRect(0, i * 20, 200, 20);
}
// now draw our off-screen canvas on the main one
ctx.drawImage(ctx2.canvas, 0, 0);
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
Or, since this is the only compositing in your composition, you can also do it all on the same, but use an other compositing mode: destination-over.
This mode will draw behind the existing content, this means that you will have to actually draw your background after you made the compositing.
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const {
width: w,
height: h
} = canvas;
//
// text compositing on a clear context
drawText();
// will draw only where the text has been drawn
ctx.globalCompositeOperation = 'source-atop';
drawRainbow();
// from here we will draw behind
ctx.globalCompositeOperation = 'destination-over';
// so we need to first draw the stars, otherwise they'll be behind
drawStars();
//And finally the sky black background
drawSky();
//... reset
ctx.globalCompositeOperation = 'source-over';
function drawSky() {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, w, h);
}
function drawStars() {
ctx.fillStyle = '#555';
let x, y, radius;
for (let i = 0; i < 550; i++) {
x = Math.random() * w;
y = Math.random() * h;
radius = Math.random() * 3;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
}
function drawText() {
ctx.font = 'bold 70pt monospace';
ctx.fillStyle = 'black';
ctx.fillText('FOO', 10, 60);
ctx.fillText('BAR', 10, 118);
}
function drawRainbow() {
for (let i = 0; i < 6; i++) {
ctx.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx.fillRect(0, i * 20, 200, 20);
}
}
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
How can I draw a non-gradient circle with colorstop, something like this:
The closest I got was using radial gradient http://jsfiddle.net/8tdz0bo4/2/:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 100,
y = 75,
innerRadius = 1,
outerRadius = 50,
radius = 60;
var gradient = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'transparent');
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
Answer is quite simple : to avoid any gradient, just build several steps having same start and end color like in :
0.0 red // first red step
0.5 red // end of first red step
0.5 blue // second blue step
1.0 blue. // end of blue step
With this idea, your code becomes :
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 100,
y = 75,
innerRadius = 1,
outerRadius = 50,
radius = 60;
var gradient = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.6, 'red');
gradient.addColorStop(0.6, 'transparent');
gradient.addColorStop(1, 'transparent');
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
<canvas id='canvas'></canvas>
Add these
gradient.addColorStop(0.2, 'red');
gradient.addColorStop(0.2, 'transparent');
http://jsfiddle.net/8tdz0bo4/3/
Is there any way to get all the values of arc on click like arc(x y radius startangle endangle anticlockwise) ?"
my code is :
function drawOval(x, y, rw, rh) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.save();
context.scale(1, rh / rw);
context.beginPath();
context.arc(x, y, rw, 0, 2 * Math.PI);
context.restore();
context.lineWidth = 4;
context.strokeStyle = "black";
context.stroke();
}
drawOval(80, 60, 50, 80);
drawOval(200, 90, 50, 50);
elem.addEventListener('click', function (event) {
alert('clicked an element');
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
console.log(x, y);
});
You can hit-test your ovals using context.isPointInPath(mouseX,mouseY).
Demo: http://jsfiddle.net/m1erickson/stmjW/
isPointInPath will hit-test the most-recently defined path (your oval).
To hit-test both your ovals, you can change your drawOval function to define--but not stroke, an oval:
function drawOval(x, y, rw, rh) {
context.save();
context.scale(1, rh / rw);
context.beginPath();
context.arc(x, y, rw, 0, 2 * Math.PI);
context.restore();
}
Then to hit-test any oval, you would
First, define the oval: drawOval(80, 60, 50, 80);
Second, hit test that defined oval: context.isPointInPath(mouseX,mouseY);
How can I divide a circle into three equal parts with HTML5 canvas 2D context API like above figure?
I was trying this
Can somebody suggest a better way? probably with percentages (or in degrees) instead of hard-coded coordinates?
var can = document.getElementById('mycanvas');
var ctx = can.getContext('2d');
ctx.fillStyle = "#BD1981";
ctx.beginPath();
ctx.arc(200, 200, 150, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "#FFC8B2";
ctx.lineWidth = "2";
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(100, 100);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(350, 200);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(100, 300);
ctx.closePath();
ctx.stroke();
Here is a function (demo) that allows you to specify a starting point, the length and the angle in degrees:
var drawAngledLine = function(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
Putting it all together (using #phant0m's drawAngledLine):
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var RADIUS = 70;
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawAngledLine(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
drawCircle(140, 140, RADIUS);
drawAngledLine(140, 140, RADIUS, 1 * (360 / 3));
drawAngledLine(140, 140, RADIUS, 2 * (360 / 3));
drawAngledLine(140, 140, RADIUS, 3 * (360 / 3));
Demo here:
http://jsfiddle.net/My8eX/
I know you probably got your answer but I found Wayne's jsfiddle helpful so I'm adding my contribution which lets you set a custom number of sections you want to divide the circle into.
http://jsfiddle.net/yorksea/3ef0y22c/2/
(also using #phant0m's drawAngledLine)
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var RADIUS = 300;
var num_sections = 19; //set this for number of divisions
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawAngledLine(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
//draw circle outline
drawCircle(320, 320, RADIUS);
//loop the number of sections to draw each
for (i = 1; i <= num_sections; i++) {
drawAngledLine(320, 320, RADIUS, i * (360 / num_sections));
}
<canvas id="canvas" width="650" height="650"></canvas>