I have an HTML canvas with all different shapes of all different sizes and it is built by parsing information from an external file. I am wondering how I can make it so that hovering the mouse over each shape will display its unique name. I have found resources on how to display text on a mouse hover for the whole canvas, but I need each individual shape to display a unique text. Thanks!
You can use context.isPointInPath to test if your mouse is hovering over one of your shapes.
Create a javascript object representing each shape from the external file.
var triangle={
name:'triangle',
color:'skyblue',
points:[{x:100,y:100},{x:150,y:150},{x:50,y:150}]
};
Create a function that takes a shape-object and makes a Path from that shape-object:
function defineShape(s){
ctx.beginPath();
ctx.moveTo(s[0].x,s[0].y);
for(var i=1;i<s.length;i++){
ctx.lineTo(s[i].x,s[i].y);
}
ctx.closePath();
}
Use context.isPointInPath to test if the mouse is inside the most recently defined path (from step#2).
// define the path to be tested
defineShape(triangle);
// test if the mouse is inside that shape
if(context.isPointInPath(mouseX,mouseY){
// the mouse is inside the shape
}
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
ctx.font='14px verdana';
var shapes=[];
var triangle1={
name:'triangle1',
color:'skyblue',
drawcolor:'skyblue',
points:[{x:100,y:100},{x:150,y:150},{x:50,y:150}]
};
var triangle2={
name:'triangle2',
color:'palegreen',
drawcolor:'palegreen',
points:[{x:220,y:100},{x:270,y:150},{x:170,y:150}]
};
shapes.push(triangle1,triangle2);
$("#canvas").mousemove(function(e){handleMouseMove(e);});
drawAll();
function drawAll(){
for(var i=0;i<shapes.length;i++){
var s=shapes[i];
defineShape(s.points);
ctx.fillStyle=s.drawcolor;
ctx.fill();
ctx.stroke();
if(s.color!==s.drawcolor){
ctx.fillStyle='black';
ctx.fillText(s.name,s.points[0].x,s.points[0].y);
}
}
}
function defineShape(s){
ctx.beginPath();
ctx.moveTo(s[0].x,s[0].y);
for(var i=1;i<s.length;i++){
ctx.lineTo(s[i].x,s[i].y);
}
ctx.closePath();
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// clear the canvas
ctx.clearRect(0,0,cw,ch);
for(var i=0;i<shapes.length;i++){
var s=shapes[i];
// define the shape path we want to test against the mouse position
defineShape(s.points);
// is the mouse insied the defined shape?
if(ctx.isPointInPath(mouseX,mouseY)){
// if yes, fill the shape in red
s.drawcolor='red';
}else{
// if no, fill the shape with blue
s.drawcolor=s.color;
}
}
drawAll();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Hover the mouse over the shape.</h4>
<canvas id="canvas" width=300 height=300></canvas>
Are you willing to use a library, or are you attached to a purely native canvas implementation? With purely native canvas, this could get quite annoying, because you would have to not only get the mouse coordinates of the pointer, but also keep track of the position of every object and check whether or not the mouse is at that position every time the mouse moves. I coded that functionality once, and it was annoying enough with just a few rectangles to keep track of.
On the other hand, if you use KineticJS to do your canvas drawing (or presumably others; KineticJS is just the one I've used), all that annoyance is handled for you. The objects you create and add to the canvas can have event handlers attached to them using the KineticJS library, and it will be only minimally harder than reacting to mouseover events on any other HTML element. This link shows how to do it.
http://www.html5canvastutorials.com/kineticjs/html5-canvas-listen-or-dont-listen-to-events-with-kineticjs/
Related
I'm developing a mapping software that renders text from database to a specific coordinate on the canvas. The objective is for the rendered text not to step on each other (not to overlap) but still follow the coordinate where it should display. The idea is, if the rendered texts overlaps, the program may opt to display it at an angle. Currently I'm rendering text via the code below:
create_point:function(x,y,stitle){
var canvas = document.getElementById('text-layer');
var context = canvas.getContext('2d');
context.fillText(stitle,x,y); // text and position
context.save();
}
Any ideas on this?
Thanks in advance :-)
Interesting mind puzzle!
Problem
You have mapped coordinates (with text labels) from your database and occasionally 2 or more coordinates are so close together that their text labels intersect (causing their text labels to be unreadable).
One solution
For each new text label to be drawn on the map:
Assume each new text label is to be drawn on the top-side of the map coordinate. Test if the new label would overwrite any existing label.
If no overwriting would take place, draw it on the top-side (and you're done with this label).
If the top-side would cause overwriting, continue to step 2.
Repeat step#1 assuming the new label would be on right-side of the map coordinate.
Repeat step#1 assuming the new label would be on bottom-side of the map coordinate.
Repeat step#1 assuming the new label would be on left-side of the map coordinate.
If all 4 steps above fail then this text label cannot be draw without overwriting existing labels.
Given failure, you have to decide on an alternate way to give the user your text label information.
These options come to mind:
Draw a small marker on the map that the user can hover over and view a popup tooltip with the text label information. This is a very common way of dealing with information that doesn't fit on the page.
Draw a small marker on the map that refers the user to a separate legend containing the text label information.
Draw a small marker on the map with an arrow-line that leads the user to a text-label that is drawn on the map but is further away from the map coordinate.
Don't include this new label at all! This new label might not be as important as existing map labels and therefore might be omitted. You can easily achieve this by sorting your map database in order of importance to the user.
Here is demo that illustrates this solution
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var fontSize=12;
var fontFace='verdana';
var dotRadius=3;
var legendX=350;
var legendY=0;
var legendYincrement=10;
var labels=[];
var nextId=0;
ctx.textAlign='left';
ctx.textBaseline='top';
ctx.font='10px arial';
ctx.strokeRect(legendX-5,0,cw-legendX+5,ch);
ctx.fillText('Other labels',legendX-3,legendY+2);
legendY+=legendYincrement;
ctx.fillText('(Color Coded)',legendX-3,legendY+2);
legendY+=legendYincrement;
var label=addLabel('Label #0',cw/2,ch/2,fontSize,fontFace,dotRadius);
drawLabel(label);
$("#canvas").mousedown(function(e){handleMouseDown(e);});
//
function addLabel(text,dotX,dotY,fontsize,fontface,dotRadius){
var font=fontsize+'px '+fontface;
ctx.font=font;
var w=ctx.measureText(text).width;
var h=fontsize*1.286;
var label={
id:nextId++,
text:text,
x:dotX-w/2,
y:dotY-dotRadius-h,
w:w,
h:h,
offsetY:0,
font:font,
isColliding:false,
dotRadius:dotRadius,
dotX:dotX,
dotY:dotY,
};
labels.push(label);
// try to position this new label in a non-colliding position
var positions=[
{ x:dotX-w/2, y:dotY-dotRadius-h }, // N
{ x:dotX+dotRadius, y:dotY-h/2 }, // E
{ x:dotX-w/2, y:dotY+dotRadius }, // S
{ x:dotX-dotRadius-w, y:dotY-h/2 }, // W
];
for(var i=0;i<positions.length;i++){
var p=positions[i];
label.x=p.x;
label.y=p.y;
label.isColliding=thisLabelCollides(label);
if(!label.isColliding){ break; }
}
//
return(label);
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
var x=parseInt(e.clientX-offsetX);
var y=parseInt(e.clientY-offsetY);
var label=addLabel('Label #'+nextId,x,y,fontSize,fontFace,dotRadius)
drawLabel(label);
}
//
function drawLabel(label){
ctx.textAlign='left';
ctx.textBaseline='top';
if(label.isColliding){
legendY+=legendYincrement;
ctx.beginPath();
ctx.arc(legendX,legendY,3,0,Math.PI*2);
ctx.fillStyle=randomColor();
ctx.fill();
ctx.font='10px arial';
ctx.fillText(label.text,legendX+5,legendY-5);
}else{
ctx.font=label.font;
ctx.fillStyle='black';
ctx.fillText(label.text,label.x,label.y)
ctx.strokeRect(label.x,label.y,label.w,label.h);
}
ctx.beginPath();
ctx.arc(label.dotX,label.dotY,label.dotRadius,0,Math.PI*2);
ctx.fill();
}
//
function thisLabelCollides(r1){
for(var i=0;i<labels.length;i++){
var r2=labels[i];
if(r1.id==r2.id || r2.isColliding){continue;}
var collides=(!(
r1.x > r2.x+r2.w ||
r1.x+r1.w < r2.x ||
r1.y > r2.y+r2.h ||
r1.y+r1.h < r2.y
));
if(collides){return(true);}
}
return(false);
}
//
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Click on the canvas to add more map labels.</h4>
<canvas id="canvas" width=450 height=300></canvas>
The solution I found is to clear the whole canvas, but I only want to delete one line not all drawings on the canvas.
What should I do?
#danielfranca is right that a line drawn onto the canvas becomes "unremembered pixels in the canvas's sea of pixels."
He's also right that saving snapshot images of the canvas as each line is drawn and reverting to one of those saved images to "delete" a line is resource intensive. (Don't use that technique!!)
But, there is an efficient way to delete previously drawn lines on a canvas!
Yes, it does clear the canvas and redraw the lines, but it's very fast & efficient...I promise!
Here's an outline of how to do it:
Define a line inside an object like this: { x0:10, y0:15, x1:100, y1:75 }
Make as many of those lines as desired and push them into an array: var lines=[];
Use the line definitions in the lines[] array to draw your lines onto the canvas.
Listen for mousemove and mousedown events.
On mousemove, iterate throught lines[] and find the line closest to the mouse. Here's a snippet of the algorithm that calculates which line is closest to a given [mx,my]:
// Find the index of the line closest to mx,my
function setClosestLine(mx,my) {
//
closestLineIndex=-1;
var minDistanceSquared=100000000;
//
// examine each line &
// determine which line is closest to the mouse (mx,my)
for(var i=0;i<lines.length;i++){
var line=lines[i];
var dx=line.x1-line.x0;
var dy=line.y1-line.y0;
var t=((mx-line.x0)*line.dx+(my-line.y0)*line.dy)/line.dx2dy2;
var x=lerp(line.x0, line.x1, t);
var y=lerp(line.y0, line.y1, t);
var dx1=mx-x;
var dy1=my-y;
var distSquared=dx1*dx1+dy1*dy1;
if(distSquared<minDistanceSquared){
minDistanceSquared=distSquared;
closestLineIndex=i;
closestX=x;
closestY=y;
}
}
};
On mousedown, use lines.splice(targetIndex,1) to remove the definition of the closest line from the lines[] array. Then clear the canvas and redraw the remaining lines.
Here's annotated code and a Demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
ctx.lineWidth=2;
// linear interpolation -- needed in setClosestLine()
var lerp=function(a,b,x){ return(a+x*(b-a)); };
// vars to track which line is closest to the mouse
var closestLineIndex=-1;
var closestX,closestY;
// make some random lines and save them in lines[]
var n=5;
var lines=[];
var randomX=function(){return(Math.random()*cw*.67);}
var randomY=function(){return(Math.random()*ch*.67);}
var lastX=randomX();
var lastY=randomY();
for(var i=0;i<n;i++){
var x=Math.random()*cw*.67;
var y=Math.random()*ch*.67;
var dx=x-lastX;
var dy=y-lastY;
var line={
x0:lastX,
y0:lastY,
x1:x,
y1:y,
weight:Math.round(Math.random()*20),
// precalc often used values
dx:dx,
dy:dy,
dx2dy2:dx*dx+dy*dy,
};
lines.push(line);
lastX=x;
lastY=y;
}
redraw();
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
//////////////////////////////
// functions
// Find the index of the line closest to mx,my
function setClosestLine(mx,my) {
//
closestLineIndex=-1;
var minDistanceSquared=100000000;
//
// examine each line &
// determine which line is closest to the mouse (mx,my)
for(var i=0;i<lines.length;i++){
var line=lines[i];
var dx=line.x1-line.x0;
var dy=line.y1-line.y0;
var t=((mx-line.x0)*line.dx+(my-line.y0)*line.dy)/line.dx2dy2;
var x=lerp(line.x0, line.x1, t);
var y=lerp(line.y0, line.y1, t);
var dx1=mx-x;
var dy1=my-y;
var distSquared=dx1*dx1+dy1*dy1;
if(distSquared<minDistanceSquared){
minDistanceSquared=distSquared;
closestLineIndex=i;
closestX=x;
closestY=y;
}
}
};
// clear & redraw all lines
function redraw(){
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// draw all lines
ctx.strokeStyle='black';
for(var i=0;i<lines.length;i++){
var line=lines[i];
ctx.beginPath();
ctx.moveTo(line.x0,line.y0);
ctx.lineTo(line.x1,line.y1);
ctx.stroke();
}
// draw the line closest to the mouse in red
if(closestLineIndex<0){return;}
var line=lines[closestLineIndex];
ctx.strokeStyle='red';
ctx.beginPath();
ctx.moveTo(line.x0,line.y0);
ctx.lineTo(line.x1,line.y1);
ctx.stroke();
}
// On mousemove, find line closest to mouse
function handleMouseMove(e){
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
setClosestLine(mouseX,mouseY);
redraw();
}
// On mousedown, remove line that was closest to mouse
function handleMouseDown(e){
e.preventDefault();
e.stopPropagation();
if(closestLineIndex>=0){
lines.splice(closestLineIndex,1);
redraw();
}
}
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>
<h4>Closest line to mouse is drawn in red<br>Click to remove that line.</h4>
<canvas id="canvas" width=300 height=300></canvas>
There's no simple way to do that, as the previous information of the pixels are lost after you draw anything.
Here you've a better answer: clear line on HTML5 Canvas
In computer graphics when drawing something, you draw to a buffer. And
when you call lineTo and stroke the buffer is updated and all
information that were in the underlying pixels are lost (or partly
lost if you use transparency) and there is no way to get it back by
undoing (unless there is an implementation containing loads of old
drawings, but that would be really heavy for the memory).
So to be able to undo a stroke might save alot of CPU/GPU time BUT
whould heavily increase the memory
So the only way seems to use clearRect.
Maybe you would give a javascript drawing library a try. There is, for example the oCanvas library, where you draw more object oriented. There is a remove function which removes drawn objects from the canvas.
I'm generating a grid of hexagons by using a for loop and I'm having some issues
for (var i=0; i <= rows; i++) {
for (var j=0; j <= cols; j++) {
ctx.save();
ctx.translate(0+i*distX, 0+j*distY);
drawHexagon(ctx);
ctx.fill();
ctx.restore();
}
}
My end goal is to create a grid of hexagons that move away from the mouse cursor when it's moving around the page, with an area of influence. I can't work out how to draw a path between each of the hexagons and I'm also having an issue with trying to animate the hexagons.
I'm still a canvas newbie, I went through the tutorials on Mozilla's developer network and all of the animations were on singular objects, not objects generated in a grid.
I was thinking that I should try and store the grid and affect it later but I'm not sure how I would go about that, I also don't think canvas works like that.
I found this which is pretty much what I want to do but I can't understand how it works:
http://codepen.io/soulwire/pen/Ffvlo
I'm fine combing through it now, if anyone could walk me through it that would be great :)
Edit: I've since gotten a grid drawn behind the dots, I'd like to manipulate this too. I still don't understand the codepen linked above, it's a little over my head.
Your link applies 2 forces:
Particles near the mouse are repelled. More specifically, if the particles centerpoint is near the mouse centerpoint, then the particle is repelled along the line between the two centerpoints.
Particles not near the mouse are attracted back to their original positions. More specifically, the particles move toward their original centerpoint along the line between their current centerpoint and their original centerpoint.
The math works like this:
// Given the mouse centerpoint (mx,my) & particle's centerpoint (px,py)
// calculate the difference between x's & y's
var dx=px-mx;
var dy=py-my;
// You can repel the particle by increasing the
// particle's position by a fraction of dx & dy
px+=dx/100;
py+=dy/100;
// And you can attract the particle by decreasing the
// particle's position by a fraction of dx & dy
px-=dx/100;
py-=dy/100;
Here's annotated code and a Demo (easing removed for ease of understanding):
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
ctx.fillStyle='skyblue';
// mouse related variables
var PI2=Math.PI*2;
var mouseRadius=75; // this is the mouse's radius of influence
var mouseRadiusSquared=mouseRadius*mouseRadius;
var mouseIsDown=false;
var mx,my;
// define a bunch of hex objects stored in an array
var hexRadius=5;
var hexPadding=5;
var hexes=[];
for(var y=hexRadius;y<ch;y+=hexRadius*2+hexPadding){
for(var x=hexRadius;x<cw;x+=hexRadius*2+hexPadding){
hexes.push({startingX:x,startingY:y,x:x,y:y});
}}
// start a continuously running ticker loop
requestAnimationFrame(tick);
// listen for mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
// draw every hex in its current position
function draw(){
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
for(var i=0;i<hexes.length;i++){
var h=hexes[i];
ctx.moveTo(h.x,h.y);
ctx.arc(h.x,h.y,hexRadius,0,PI2);
ctx.closePath();
}
ctx.fill();
}
// create a continuously running ticker
function tick(time){
// update each hex position based on its
// position relative to the mouse
for(var i=0;i<hexes.length;i++){
var h=hexes[i];
// calculate if this hex is inside the mouse radius
var dx=h.x-mx;
var dy=h.y-my;
if(mouseIsDown && dx*dx+dy*dy<mouseRadiusSquared){
// hex is inside mouseRadius
// so mouseDown repels hex
h.x+=dx/120;
h.y+=dy/120;
}else if(h.x==h.startingX && h.y==h.startingY){
// hex is at startingX/Y & is not being repelled
// so do nothing
}else{
// hex has moved off startingX/Y
// but is no longer being repelled
// so gravity attracts hex back to its startingX/Y
dx=h.x-h.startingX;
dy=h.y-h.startingY;
h.x-=dx/60;
h.y-=dy/60;
}
}
// redraw the hexes in their new positions
draw();
// request another tick
requestAnimationFrame(tick);
}
// listen for mousedown events
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// calculate the mouse position
mx=parseInt(e.clientX-offsetX);
my=parseInt(e.clientY-offsetY);
// set the mousedown flag
mouseIsDown=true;
}
// listen for mouseup events
function handleMouseUp(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// clear the mousedown flag
mouseIsDown=false;
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Press the mouse down to repel the particles.<br>Release to return particles to starting point.</h4>
<canvas id="canvas" width=300 height=300></canvas>
I have a contract text which is stored into MS SQL VARMAX field. When loaded into a canvas I need to have the user signed in different places. Then need to save the signatures as svg format. But how do I grab the relative positions of each signature? So when I load back the text I can show the signatures in the right positions.
Thanks,
Doron
When the person initially signs the contract, presumably you will have a separate canvas positioned directly over your contract text. The signatory will move the mouse/pen over the canvas to create their signature. The signature will be the only opaque pixels on the canvas.
You can use getImageData to fetch the pixel data for the canvas. Then you can loop through each pixel and save the leftmost, topmost, rightmost and bottommost opaque pixel position. That gives your desired bounding box for the signature.
Here's how to get the bounding box of a signature on the canvas.
function findBoundary(){
var data=ctx.getImageData(0,0,cw,ch).data;
var top=1000000;
var left=1000000;
var right=-10;
var bottom=-10;
for(var y=0;y<ch;y++){
for(var x=0;x<cw;x++){
var n=(y*cw+x)*4+3;
if(data[n]>200){
if(x<left){left=x;}
if(x>right){right=x;}
if(y<top){top=y;}
if(y>bottom){bottom=y;}
}
}}
return({x:left,y:top,width:right-left+1,height:bottom-top+1});
}
Example code and a Demo:
Note: This simple demo isn't meant to be a signature capture app--I assume you already have a full signature app. It just draws a stoke as you drag the mouse and shows you the bounding box of the stroke when you release the mouse.
var $results=$('#results');
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
ctx.lineCap = "round";
ctx.lineWidth=2;
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var scrollX=$canvas.scrollLeft();
var scrollY=$canvas.scrollTop();
var isDown=false;
var startX;
var startY;
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUpOut(e);});
$("#canvas").mouseout(function(e){handleMouseUpOut(e);});
function findBoundary(){
var data=ctx.getImageData(0,0,cw,ch).data;
var top=1000000;
var left=1000000;
var right=-10;
var bottom=-10;
for(var y=0;y<ch;y++){
for(var x=0;x<cw;x++){
var n=(y*cw+x)*4+3;
if(data[n]>200){
if(x<left){left=x;}
if(x>right){right=x;}
if(y<top){top=y;}
if(y>bottom){bottom=y;}
}
}}
return({x:left,y:top,width:right-left+1,height:bottom-top+1});
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
ctx.clearRect(0,0,cw,ch);
ctx.lineWidth=3;
ctx.strokeStyle='black';
isDown=true;
}
function handleMouseUpOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
isDown=false;
var pos=findBoundary();
ctx.strokeStyle='red';
ctx.lineWidth=1;
ctx.globalAlpha=0.50;
ctx.strokeRect(pos.x,pos.y,pos.width,pos.height);
ctx.globalAlpha=1.00;
$results.text(
'Signature boundary x/y: '
+parseInt(pos.x)+'/'
+parseInt(pos.y)+', width/height: '
+parseInt(pos.width)+'/'
+parseInt(pos.height)
);
}
function handleMouseOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseOut stuff here
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
ctx.beginPath();
ctx.moveTo(startX,startY);
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
startX=mouseX;
startY=mouseY;
}
body{ background-color: white; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4 id='results'>Sign by dragging</h4>
<canvas id="canvas" width=300 height=300></canvas>
[ Addition based on additional question ]
In addition to the signature there could be 2 initials some where on
the contract...How would you hand that?
That involves set recognition through establishing a bounding box.
Find the row containing the topmost opaque pixel,
Move downward by row until you have sufficient rows with no opaque pixels (== until you're sure the signature is vertically complete).
Find the column containing the leftmost opaque pixel,
Move rightward by column until you have sufficient rows with no opaque pixels (== until you're sure the signature is horizontally complete).
The bounding box of the signature is the left/right and top/bottom pixels determined in #1-4.
Erase the pixels in the bounding box.
At this point you've successfully captured the first signature (or initials).
Repeat steps #1-6 until you find no pixels (== until there are no more signatures or initials).
Good luck with your project!
Just wondering if anyone could point me in a good direction to a way I could fill an irregular shape with particles, in rows, which would then be animatable.
This is the closest example i can find - http://www.wkams.com/#!/work/detail/coca-cola-music-vis
The two ways I can think would work is work out the density I want, map out how many particles would be needed for each row, and position accordingly. This way seems quite timely and not very robust.
The second way, which I can't seem to figure out how I would do it, is draw the shape in the canvas, then generatively fill the shape with particles, keeping them in the constraints of the shape.
Any general concept of how this could be done would be greatly appreciated.
Let me know if it doesn't make sense.
Cheers
You can use compositing to restrict your particles inside an irregular shape
For each loop of your animation:
Clear the canvas.
Draw your irregular shape on the canvas.
Set compositing to 'source-atop'. This will cause any new drawings to appear only if any newly drawn pixel is over an existing opaque pixel. This is the secret to restricting your particles to be drawn only inside your irregular shape.
Draw your rows of particles. All particles will appear only inside the shape.
Here's example code and a Demo. My example just animates the size of each particle row. You can apply your design requirements to change the size & position of each row.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
//
ctx.fillStyle='skyblue';
var PI2=Math.PI*2;
//
var w=132;
var h=479;
//
var x1=29;
var x2=177;
var x3=327;
//
var nextTime=0;
var delay=16*2;
var isFading=true;
var isComplete=false;
var opacity=100;
var imgCount=2;
var img=new Image();img.onload=start;img.src="https://dl.dropboxusercontent.com/u/139992952/multple/coke.png";
var label=new Image();label.onload=start;label.src="https://dl.dropboxusercontent.com/u/139992952/multple/label.png";
function start(){
console.log(imgCount);
if(--imgCount>0){return;}
requestAnimationFrame(animate);
$('#again').click(function(){
nextTime=0;
delay=16*2;
opacity=100;
isFading=true;
});
}
function overlay(clipX,x,alpha){
ctx.globalAlpha=alpha;
ctx.drawImage(img,clipX,0,w,h,x,0,w,h);
}
function fillParticles(radius,margin){
var rr=radius*2+margin;
ctx.save();
ctx.clearRect(0,0,cw,ch);
overlay(x3,50,1.00);
ctx.globalCompositeOperation='source-atop';
ctx.beginPath();
var rows=parseInt(ch/(rr))-2;
var cols=parseInt(cw/rr);
for(var r=0;r<rows;r++){
for(var c=0;c<cols;c++){
ctx.arc(c*rr,h-(r*rr),radius,0,PI2);
ctx.closePath();
}}
ctx.fill();
ctx.restore();
overlay(x2,50,1.00);
}
function animate(time){
if(!isComplete){ requestAnimationFrame(animate); }
if(time<nextTime){return;}
if(isFading){
if(--opacity>0){
ctx.clearRect(0,0,cw,ch);
overlay(x1,50,opacity/100);
overlay(x2,50,1.00);
}else{
isFading=false;
overlay(x2,50,1.00);
ctx.drawImage(label,70,210);
nextTime=time+1000;
}
}else{
delay=1000;
fillParticles(parseInt(Math.random()*8)+2,3);
ctx.drawImage(label,70,210);
nextTime=time+delay;
}
}
body{ background-color:white; padding:10px; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id=again>Again</button>
<br>
<canvas id="canvas" width=250 height=500></canvas>
If I were to approach this problem, I would go about it in this way:
Create an object that can be used to "create" particles.
Create as many new instances of that object as is needed for the required density.
So, basically, all the work is done by one function constructor/object.
You want this object to provide methods to draw itself to the canvas, store its x and y coordinates, its velocity and direction.
Then you can create instances of this object with the new keyword and set their x and y coordinates to spread them across a grid.