I bumped into the following problem, hope someone will know how to help me:
I work with the JavaScript library Raphael. Now, what I want to do is, when I have many Raphael SVG elements, to simply select more elements with "rectangle selection", i.e. by dragging the mouse starting from the graph's background to create a selection rectangle (I hope I was clear enough), and move the elements which are in this rectangle.
For now, I've found something like this (someone posted it from a previous question of mine):
var paper = Raphael(0, 0, '100%', '100%');
var circle = paper.circle(75, 75, 50);
var rect = paper.rect(150, 150, 50, 50);
var set = paper.set();
set.push(circle, rect);
set.attr({
fill: 'red',
stroke: 0
});
var ox = 0;
var oy = 0;
var dragging = false;
set.mousedown(function(event) {
ox = event.screenX;
oy = event.screenY;
set.attr({
opacity: .5
});
dragging = true;
});
set.mousemove(function(event) {
if (dragging) {
set.translate(event.screenX - ox, event.screenY - oy);
ox = event.screenX;
oy = event.screenY;
}
});
set.mouseup(function(event) {
dragging = false;
set.attr({
opacity: 1
});
});
This code can be executed on jsfiddle. But, as you can see, this selects ALL the elements, by simply adding them to a Raphael set.
Now, I think that my problem will be solved by:
Making a rectangle selection
Adding the nodes which are in the rectangle to a Raphael set
Move only the selected items (i.e. move only the items which are in the Raphael set with set.mousemove)
My problem for now would be the first two issues.
Any ideas how to do this?
Thank you in advance!
Fun problem. You can do this by placing a rectangular "mat" the size of the canvas behind all of your other objects and attaching a drag event to it for selecting other elements. (Note this solution uses the newer version of Raphael, 2.1.0:
var paper = Raphael(0, 0, '100%', '100%');
//make an object in the background on which to attach drag events
var mat = paper.rect(0, 0, paper.width, paper.height).attr("fill", "#FFF");
var circle = paper.circle(75, 75, 50);
var rect = paper.rect(150, 150, 50, 50);
var set = paper.set();
set.push(circle, rect);
set.attr({
fill: 'red',
stroke: 0
});
//the box we're going to draw to track the selection
var box;
//set that will receive the selected items
var selections = paper.set();
Now, we add a drag event -- similar to the mouseover events but with three functions (see documentation), and draw a box to track the selection space:
//DRAG FUNCTIONS
//when mouse goes down over background, start drawing selection box
function dragstart (x, y, event) {
box = paper.rect(x, y, 0, 0).attr("stroke", "#9999FF");
}
// When mouse moves during drag, adjust box.
// If the drag is to the left or above original point,
// you have to translate the whole box and invert the dx
// or dy values since .rect() doesn't take negative width or height
function dragmove (dx, dy, x, y, event) {
var xoffset = 0,
yoffset = 0;
if (dx < 0) {
xoffset = dx;
dx = -1 * dx;
}
if (dy < 0) {
yoffset = dy;
dy = -1 * dy;
}
box.transform("T" + xoffset + "," + yoffset);
box.attr("width", dx);
box.attr("height", dy);
}
function dragend (event) {
//get the bounds of the selections
var bounds = box.getBBox();
box.remove();
reset();
console.log(bounds);
for (var c in set.items) {
// Here, we want to get the x,y vales of each object
// regardless of what sort of shape it is.
// But rect uses rx and ry, circle uses cx and cy, etc
// So we'll see if the bounding boxes intercept instead
var mybounds = set[c].getBBox();
//do bounding boxes overlap?
//is one of this object's x extremes between the selection's xe xtremes?
if (mybounds.x >= bounds.x && mybounds.x <= bounds.x2 || mybounds.x2 >= bounds.x && mybounds.x2 <= bounds.x2) {
//same for y
if (mybounds.y >= bounds.y && mybounds.y <= bounds.y2 || mybounds.y2 >= bounds.y && mybounds.y2 <= bounds.y2) {
selections.push(set[c]);
}
}
selections.attr("opacity", 0.5);
}
}
function reset () {
//empty selections and reset opacity;
selections = paper.set();
set.attr("opacity", 1);
}
mat.drag(dragmove, dragstart, dragend);
mat.click(function(e) {
reset();
});
Just like that, you have a new set (selections) that contains every object that was selected by the mouse drag. You can then apply your mouseover events from the original to that set.
Note that this will select circle objects if you nick the corner of their bounding box with your selection box, even if it doesn't overlap with the area of the circle. You could make a special case for circles if this is a problem.
jsFiddle
Related
I have started playing with P5JS, but I wanted to create a tooltip on mouse over for each of the data points when the pointer is over it.
How can I do this? I have seen some examples using the map function, but unsure how this would work here.
Appreciate any help! I am not a JS dev just yet!
Generally speaking displaying tooltips that are specific to graphics displayed on a canvas involves "hit testing," which is to say: checking if the mouse is hovering over the specific graphics, and then some mechanism for displaying the tooltip.
Since your graphics are all circles, hit testing is quite simple. For each circle that you draw, check the distance to the mouse position. If the distance is less than the radius of the circle than the mouse is hovering over that circle:
let mouseDist = dist(posX, posY, mouseX, mouseY);
if (mouseDist < earthquakeMag * 5) {
// display tooltip, or set a flag to display the
// tooltip after the rest of the graphics have been
// displayed
}
As for displaying the tooltip you have two options: 1) you can rely on the native behavior of browser elements by setting the title attribute of the canvas element, or 2) you can display your own tooltip. The advantage of the former is that it is very trivial, but the advantage of the latter is that you have more control over where/when/how the tooltip is rendered.
Option 1:
let c;
function setup() {
c = createCanvas(500,500);
// ...
}
function draw() {
// ...
for (var i = 0; i < this.data.getRowCount(); i++) {
// ...
let mouseDist = dist(posX, posY, mouseX, mouseY);
if (mouseDist < earthquakeMag * 5) {
c.elt.title = 'hit!';
}
}
}
Option 2:
let tooltipText;
for (var i = 0; i < this.data.getRowCount(); i++) {
// ...
let mouseDist = dist(posX, posY, mouseX, mouseY);
if (mouseDist < earthquakeMag * 5) {
// If we displayed the tooltip at this point then
// some of the circles would overlap it.
tooltipText = date_[i];
}
// ...
}
if (tooltipText) {
// measure the width of the tooltip
let w = textWidth(tooltipText);
// save the current fill/stroke/textAlign state
push();
// draw a lightgray rectangle with a dimgray border
fill('lightgray');
stroke('dimgray');
strokeWeight(1);
// draw this rectangle slightly below and to the
// right of the mouse
rect(mouseX + 10, mouseY + 10, w + 20, 24, 4);
textAlign(LEFT, TOP);
noStroke();
fill('black');
text(tooltipText, mouseX + 20, mouseY + 16);
// restore the previous fill/stroke/textAlign state
pop();
}
I have these event listeners in my code
canvas.addEventListener('mousemove', onMouseMove, false);
canvas.addEventListener('mousedown', onMouseDown,false);
canvas.addEventListener('mouseup', onMouseUp, false);
These functions will help me to pan the canvas. I have declared a variable in the onLoad called pan, isDown, mousePostion and previous mouse positions. Then in the initialise function is set the pan,mousePos and premousepos to vectors containing 0,0
function draw() {
context.translate(pan.getX(), pan.getY());
topPerson.draw(context);
console.log(pan);
}
function onMouseDown(event) {
var x = event.offsetX;
var y = event.offsetY;
var mousePosition = new vector(event.offsetX, event.offsetY);
previousMousePosition = mousePosition;
isDown = true;
console.log(previousMousePosition);
console.log("onmousedown" + "X coords: " + x + ", Y coords: " + y);
}
function onMouseUp(event) {
isDown = false;
}
function onMouseMove(event) {
if (isDown) {
console.log(event.offsetX);
mousePosition = new vector(event.offsetX, event.offsetY);
newMousePosition = mousePosition;
console.log('mouseMove' + newMousePosition);
var panX = newMousePosition.getX() - previousMousePosition.getX();
var panY = newMousePosition.getY() - previousMousePosition.getY();
console.log('onMouseMove: ' + panX);
pan = new vector(panX, panY);
console.log('mouseMove' + pan);
}
}
But it is not registering the new pan Values so you could attempt to drag the canvas. I know my mouse dragging events work but is just doesnt pan.
Here's a simple (annotated) example of panning code
It works by accumulating the net amount the mouse has been dragged horizontally (and vertically) Then it redraws everything, but offset by those accumulated horizontal & vertical distances.
Example 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;
// account for scrolling
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(); }
// mouse drag related variables
var isDown=false;
var startX,startY;
// the accumulated horizontal(X) & vertical(Y) panning the user has done in total
var netPanningX=0;
var netPanningY=0;
// just for demo: display the accumulated panning
var $results=$('#results');
// draw the numbered horizontal & vertical reference lines
for(var x=0;x<100;x++){ ctx.fillText(x,x*20,ch/2); }
for(var y=-50;y<50;y++){ ctx.fillText(y,cw/2,y*20); }
// listen for mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// calc the starting mouse X,Y for the drag
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// set the isDragging flag
isDown=true;
}
function handleMouseUp(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// clear the isDragging flag
isDown=false;
}
function handleMouseOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// clear the isDragging flag
isDown=false;
}
function handleMouseMove(e){
// only do this code if the mouse is being dragged
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// dx & dy are the distance the mouse has moved since
// the last mousemove event
var dx=mouseX-startX;
var dy=mouseY-startY;
// reset the vars for next mousemove
startX=mouseX;
startY=mouseY;
// accumulate the net panning done
netPanningX+=dx;
netPanningY+=dy;
$results.text('Net change in panning: x:'+netPanningX+'px, y:'+netPanningY+'px');
// display the horizontal & vertical reference lines
// The horizontal line is offset leftward or rightward by netPanningX
// The vertical line is offset upward or downward by netPanningY
ctx.clearRect(0,0,cw,ch);
for(var x=-50;x<50;x++){ ctx.fillText(x,x*20+netPanningX,ch/2); }
for(var y=-50;y<50;y++){ ctx.fillText(y,cw/2,y*20+netPanningY); }
}
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 id=results>Drag the mouse to see net panning in x,y directions</h4>
<canvas id="canvas" width=300 height=150></canvas>
To answer question
You have not provided some of the code. Specifically the vector object you are creating each event, could be there. (really you should not be creating a new object each time. Create once and update the values)
What I do see is that mouseMove events do not update the previous mouse position object so you will only get panning from the last mouse down. But you may want that. So without the code I don't know what is wrong as the code given is OK.
Below is how I do the whole shabang..
How to pan (and zoom).
Below is an example of panning and zooming with the mouse. Its a little more complex than standard pan and zooms, that is because I have added some smoothing to the pan and zoom to give it a better interactive feel.
How it works.
The canvas uses a transformation matrix to transform points. What this does is maintain that matrix. I call the transformed space, real space. I also maintain an inverse matrix, that is used to convert from screen space into real space.
The core of the demo is around the object displayTransform it holds the matrix, all the individual values needed, and the functions update() call once a frame, setHome() get the screen space transform and applies it to the canvas. Used to clear the screen. And setTransform() this set the canvas to real space (the zoomed panned space)
To smooth out movements I have a mirror of the values x, y, ox, oy, scale, and rotate. ((ox,oy) are origin x and y) (and yes rotate works) each of these variable has a delta prefixed with d and a chaser prefixed with c. The chaser values chase the required values. You should not touch the chaser values. There are two values called drag and accel (short for acceleration) drag (not real simulated drag) is how quickly the deltas decay. Values for drag > 0.5 will result in a bouncy response. As you get toward one it will get more and more bouncy. At 1 the bound will not stop, above one and it's unusable. 'accel' is how quickly the transform responds to mouse movement. Low values are slow response, 0 is no response at all, and one is instant response. Play with the values to find what you like.
Example of the logic for chaser values
var x = 100; // the value to be chased
var dx = 0; // the delta x or the change in x per frame
var cx = 0; // the chaser value. This value chases x;
var drag = 0.1; // quick decay
var accel = 0.9; // quick follpw
// logic
dx += (x-cx)*accel; // get acceleration towards x
dx *= drag; // apply the drag
cx += dx; // change chaser by delta x.
Convert coords
No point having a zoom panned rotated canvas if you don't know where things are. To do this I keep an inverse matrix. It converts screen x and y into realspace x and y. For convenience I convert the mouse to real space every update. If you want the reverse realSpace to screen space. then its just
var x; // real x coord (position in the zoom panned rotate space)
var y; // real y coord
// "this" is displayTransform
x -= this.cx;
y -= this.cy;
// screenX and screen Y are the screen coordinates.
screenX = (x * this.matrix[0] + y * this.matrix[2])+this.cox;
screenY = (x * this.matrix[1] + y * this.matrix[3])+this.coy;
You can see it at the end of the mouse displayTransform.update where I use the inverse transform to convert the mouse screen coords to real coords. Then in the main update loop I use the mouse real coords to display the help text. I leave it up to the user of the code to create a function that will convert any screen coord. (easy just pinch the bit where the mouse is being converted).
Zoom
The zoom is done with the mouse wheel. This presents a bit of a problem and naturally you expect the zoom to be centered on the mouse. But the transform is actually relative to the top left of the screen. To fix this I also keep an origin x and y. This basically floats about until the wheel zoom is needed then it is set to the mouse real position, and the mouse distance from the top left is placed in the transform x and y position. Then just increase or decrease the scale to zoom in and out. I have left the origin and offset to float (not set the chase values) this works for the current drag and acceleration setting but if you notice that it's not working that well with other setting set the the cx, cy, cox, coy values as well. ( I have added a note in the code)
Pan
Pan is done with the left mouse button. Click and drag to pan. This is straight forward. I get the difference between the last mouse position and the new one screen space (the coords given by the mouse events) This gives me a mouse delta vector. I transform the delta mouse vector into real space and subtract that from the top left coords displayTransform.x and displayTransform.y. Thats it I let the chaser x and y smooth it all out.
The snippet just displays a large image that can be panned and zoomed. I check for the complete flag rather than use onload. While the image is loading the snippet will just display loading. The main loop is refreshed with requestAnimationFrame, first I update the displayTransform then the canvas is cleared in home space (screen space) and then the image is displayed in real space. As always I a fighting time so will return as time permits to add more comments, and maybe a function or two.
If you find the chase variables a little to much, you can just remove them and replace all the c prefixed vars with the unprefixed ones.
OK hope this helps. Not done yet as need to clean up but need to do some real work for a bit.
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonLastRaw : 0, // user modified value
buttonRaw : 0,
over : false,
buttons : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
};
function mouseMove(event) {
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if (mouse.x === undefined) {
mouse.x = event.clientX;
mouse.y = event.clientY;
}
mouse.alt = event.altKey;
mouse.shift = event.shiftKey;
mouse.ctrl = event.ctrlKey;
if (event.type === "mousedown") {
event.preventDefault()
mouse.buttonRaw |= mouse.buttons[event.which-1];
} else if (event.type === "mouseup") {
mouse.buttonRaw &= mouse.buttons[event.which + 2];
} else if (event.type === "mouseout") {
mouse.buttonRaw = 0;
mouse.over = false;
} else if (event.type === "mouseover") {
mouse.over = true;
} else if (event.type === "mousewheel") {
event.preventDefault()
mouse.w = event.wheelDelta;
} else if (event.type === "DOMMouseScroll") { // FF you pedantic doffus
mouse.w = -event.detail;
}
}
function setupMouse(e) {
e.addEventListener('mousemove', mouseMove);
e.addEventListener('mousedown', mouseMove);
e.addEventListener('mouseup', mouseMove);
e.addEventListener('mouseout', mouseMove);
e.addEventListener('mouseover', mouseMove);
e.addEventListener('mousewheel', mouseMove);
e.addEventListener('DOMMouseScroll', mouseMove); // fire fox
e.addEventListener("contextmenu", function (e) {
e.preventDefault();
}, false);
}
setupMouse(canvas);
// terms.
// Real space, real, r (prefix) refers to the transformed canvas space.
// c (prefix), chase is the value that chases a requiered value
var displayTransform = {
x:0,
y:0,
ox:0,
oy:0,
scale:1,
rotate:0,
cx:0, // chase values Hold the actual display
cy:0,
cox:0,
coy:0,
cscale:1,
crotate:0,
dx:0, // deltat values
dy:0,
dox:0,
doy:0,
dscale:1,
drotate:0,
drag:0.1, // drag for movements
accel:0.7, // acceleration
matrix:[0,0,0,0,0,0], // main matrix
invMatrix:[0,0,0,0,0,0], // invers matrix;
mouseX:0,
mouseY:0,
ctx:ctx,
setTransform:function(){
var m = this.matrix;
var i = 0;
this.ctx.setTransform(m[i++],m[i++],m[i++],m[i++],m[i++],m[i++]);
},
setHome:function(){
this.ctx.setTransform(1,0,0,1,0,0);
},
update:function(){
// smooth all movement out. drag and accel control how this moves
// acceleration
this.dx += (this.x-this.cx)*this.accel;
this.dy += (this.y-this.cy)*this.accel;
this.dox += (this.ox-this.cox)*this.accel;
this.doy += (this.oy-this.coy)*this.accel;
this.dscale += (this.scale-this.cscale)*this.accel;
this.drotate += (this.rotate-this.crotate)*this.accel;
// drag
this.dx *= this.drag;
this.dy *= this.drag;
this.dox *= this.drag;
this.doy *= this.drag;
this.dscale *= this.drag;
this.drotate *= this.drag;
// set the chase values. Chase chases the requiered values
this.cx += this.dx;
this.cy += this.dy;
this.cox += this.dox;
this.coy += this.doy;
this.cscale += this.dscale;
this.crotate += this.drotate;
// create the display matrix
this.matrix[0] = Math.cos(this.crotate)*this.cscale;
this.matrix[1] = Math.sin(this.crotate)*this.cscale;
this.matrix[2] = - this.matrix[1];
this.matrix[3] = this.matrix[0];
// set the coords relative to the origin
this.matrix[4] = -(this.cx * this.matrix[0] + this.cy * this.matrix[2])+this.cox;
this.matrix[5] = -(this.cx * this.matrix[1] + this.cy * this.matrix[3])+this.coy;
// create invers matrix
var det = (this.matrix[0] * this.matrix[3] - this.matrix[1] * this.matrix[2]);
this.invMatrix[0] = this.matrix[3] / det;
this.invMatrix[1] = - this.matrix[1] / det;
this.invMatrix[2] = - this.matrix[2] / det;
this.invMatrix[3] = this.matrix[0] / det;
// check for mouse. Do controls and get real position of mouse.
if(mouse !== undefined){ // if there is a mouse get the real cavas coordinates of the mouse
if(mouse.oldX !== undefined && (mouse.buttonRaw & 1)===1){ // check if panning (middle button)
var mdx = mouse.x-mouse.oldX; // get the mouse movement
var mdy = mouse.y-mouse.oldY;
// get the movement in real space
var mrx = (mdx * this.invMatrix[0] + mdy * this.invMatrix[2]);
var mry = (mdx * this.invMatrix[1] + mdy * this.invMatrix[3]);
this.x -= mrx;
this.y -= mry;
}
// do the zoom with mouse wheel
if(mouse.w !== undefined && mouse.w !== 0){
this.ox = mouse.x;
this.oy = mouse.y;
this.x = this.mouseX;
this.y = this.mouseY;
/* Special note from answer */
// comment out the following is you change drag and accel
// and the zoom does not feel right (lagging and not
// zooming around the mouse
/*
this.cox = mouse.x;
this.coy = mouse.y;
this.cx = this.mouseX;
this.cy = this.mouseY;
*/
if(mouse.w > 0){ // zoom in
this.scale *= 1.1;
mouse.w -= 20;
if(mouse.w < 0){
mouse.w = 0;
}
}
if(mouse.w < 0){ // zoom out
this.scale *= 1/1.1;
mouse.w += 20;
if(mouse.w > 0){
mouse.w = 0;
}
}
}
// get the real mouse position
var screenX = (mouse.x - this.cox);
var screenY = (mouse.y - this.coy);
this.mouseX = this.cx + (screenX * this.invMatrix[0] + screenY * this.invMatrix[2]);
this.mouseY = this.cy + (screenX * this.invMatrix[1] + screenY * this.invMatrix[3]);
mouse.rx = this.mouseX; // add the coordinates to the mouse. r is for real
mouse.ry = this.mouseY;
// save old mouse position
mouse.oldX = mouse.x;
mouse.oldY = mouse.y;
}
}
}
// image to show
var img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/e/e5/Fiat_500_in_Emilia-Romagna.jpg"
// set up font
ctx.font = "14px verdana";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// timer for stuff
var timer =0;
function update(){
timer += 1; // update timere
// update the transform
displayTransform.update();
// set home transform to clear the screem
displayTransform.setHome();
ctx.clearRect(0,0,canvas.width,canvas.height);
// if the image loaded show it
if(img.complete){
displayTransform.setTransform();
ctx.drawImage(img,0,0);
ctx.fillStyle = "white";
if(Math.floor(timer/100)%2 === 0){
ctx.fillText("Left but to pan",mouse.rx,mouse.ry);
}else{
ctx.fillText("Wheel to zoom",mouse.rx,mouse.ry);
}
}else{
// waiting for image to load
displayTransform.setTransform();
ctx.fillText("Loading image...",100,100);
}
if(mouse.buttonRaw === 4){ // right click to return to homw
displayTransform.x = 0;
displayTransform.y = 0;
displayTransform.scale = 1;
displayTransform.rotate = 0;
displayTransform.ox = 0;
displayTransform.oy = 0;
}
// reaquest next frame
requestAnimationFrame(update);
}
update(); // start it happening
.canC { width:400px; height:400px;}
div {
font-size:x-small;
}
<div>Wait for image to load and use <b>left click</b> drag to pan, and <b>mouse wheel</b> to zoom in and out. <b>Right click</b> to return to home scale and pan. Image is 4000 by 2000 plus so give it time if you have a slow conection. Not the tha help text follows the mouse in real space. Image from wiki commons</div>
<canvas class="canC" id="canV" width=400 height=400></canvas>
Aim of my code:
Draw a small rectangle on a HTML canvas whenever a user clicks the canvas. The rectangle should have a small number representing the number of rectangles made by the user.
The user should be able to connect any two rectangles using a straight line. (Preferably by just pressing left mouse button, and taking the mouse from first rectangle to second rectangle)
Approach and my attempt
As you can see in this jsFiddle , I have been able to achieve the first part of above very well. On clicking on the canvas, a rectangle with a number inside of it is made. But I am really clueless about the second part.
How do I make the user connect any two made rectangles? I want the connection to be made only if a rectangle is there ( So I would need to store coordinates of every rectangle that has been made, that's okay as I can use an array for that ).
Basically, I just want to check if the mousedown was at one place and mouseup at the other.
How do I get these two different coordinates ( one of mousedown and other of mouseup ) , and draw a line between them?
I have given the Fiddle above but still here's my jquery:
$(function () {
var x, y;
var globalCounter = 0;
$('#mycanvas').on("click", function (event) {
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
x -= mycanvas.offsetLeft;
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
y -= mycanvas.offsetLeft;
// alert("x:"+x+"y: "+y);
drawRectangle(x, y);
});
function drawRectangle(x, y) {
var acanvas = document.getElementById("mycanvas");
var context = acanvas.getContext("2d");
context.strokeRect(x, y, 25, 25);
globalCounter++;
writeNo(x, y, globalCounter);
}
function writeNo(x, y, n) {
var acanvas = document.getElementById("mycanvas");
var context = acanvas.getContext("2d");
context.font = "bold 14px sans-serif";
context.fillText(n, x + 8, y + 12.5);
}
});
The main question is therefore: connecting the two made rectangles by mousedrag
How do I achieve this?
Thank You.
How about this: http://jsfiddle.net/4jqptynt/4/
Ok, first I did a little refactoring for your code to make things easier. Just stuff like putting the code that gets the canvas coordinates into it's own function, and caching some variables (like the canvas context) in the outer function's scope. Oh, and defining your rectangle dimensions as constants because we'll be using the same numbers in a couple of different places.
As you said, the first thing we need is to keep track of the existing rectangles using an array rects (easy enough to do within drawRectangle). Then we need a function to check if a particular pair of coordinates are within some rectangle:
function inRectangle(x, y) {
for (var i = 0, l = rects.length; i < l; i++) {
if ((x - rects[i].x) <= RECT_X && (y - rects[i].y) <= RECT_Y &&
(x - rects[i].x) >= 0 && (y - rects[i].y) >= 0) {
return i;
}
}
}
where RECT_X & RECT_Y define the sides of the rectangle. If the coordinates do exist within some rectangle then this will return the index of that rectangle within the rects array.
Then it's a case of checking whether or not a mousedown occurred within a rectangle, noting that inRectangle will only return a number if the mousedown event was within a rectangle:
$acanvas.on("mousedown", function (event) {
var coords = getCoords(event),
rect = inRectangle(coords.x, coords.y);
if (typeof rect === "number") {
dragStart = rect + 1;
} else {
drawRectangle(coords.x, coords.y);
}
});
if so, make a note of which rectangle using dragStart, if not draw a rectangle as before.
Then to complete the drag, we need to attach a handler to mouseup:
$acanvas.on("mouseup", function (event) {
if (!dragStart) { return; }
var coords = getCoords(event),
rect = inRectangle(coords.x, coords.y);
if (typeof rect === "number") {
drawConnection(dragStart - 1, rect);
}
dragStart = 0;
});
If no drag was started, then it does nothing. If it's coordinates aren't within a rectangle, then it does nothing but reset dragStart. If however, it is within a rectangle, then it draws a connecting line:
function drawConnection(rect1, rect2) {
context.strokeStyle = "black";
context.lineWidth = 1;
context.beginPath();
context.moveTo(rects[rect1].x + RECT_X/2, rects[rect1].y + RECT_Y/2);
context.lineTo(rects[rect2].x + RECT_X/2, rects[rect2].y + RECT_Y/2);
context.stroke();
context.closePath();
}
In the following fiddle, you can click and drag around the image, and it will not be able to exit the blue border. By clicking the red and green rectangles, you can rotate the image. However when you click and drag a rotated object, the image does not follow the mouse. I would like the image to follow the mouse even if it is rotated.
http://jsfiddle.net/n3Sn5/
I think the issue occurs within my move function
move = function (dx, dy)
{
nowX = Math.min(boundary.attr("x")+boundary.attr("width")-this.attr("width"), this.ox + dx);
nowY = Math.min(boundary.attr("y")+boundary.attr("height")-this.attr("height"), this.oy + dy);
nowX = Math.max(boundary.attr("x"), nowX);
nowY = Math.max(boundary.attr("y"), nowY);
this.attr({x: nowX, y: nowY });
}
One thing to notice is that when you click and drag a rotated object, after you release your mouse click, if you rotate the image, it snaps to where your mouse was when you released the mouse click, even obeying the boundary.
I was able to get the rotated image to drag with the mouse previously, but by adding the boundary rectangle, i had to use a more complex approach.
If anyone has an idea of what I need to change, I would be very grateful!
Thanks!
The required output can be achieved in a bit different way. Please check the fiddle at http://jsfiddle.net/6BbRL/. I have trimmed to code to keep the basic parts for demo.
var paper = Raphael(0, 0, 475, 475),
boxX = 100,
boxY = 100,
boxWidth = 300,
boxHeight = 200,
// EDITED
imgWidth = 50,
imgHeight = 50,
box = paper.rect(boxX, boxY, boxWidth, boxHeight).attr({fill:"#ffffff"}),
// EDITED
html5 = paper.image("http://www.w3.org/html/logo/downloads/HTML5_Badge_512.png",boxX+boxWidth-imgWidth,boxY+boxHeight-imgHeight,imgWidth,imgHeight)
.attr({cursor: "move"}),
elementCounterClockwise = paper.rect(180, 0, 50, 50).attr({fill:"#ff5555", cursor:"pointer"}),
elementClockwise = paper.rect(250, 0, 50, 50).attr({ fill: "#55ff55", cursor: "pointer" }),
boundary = paper.rect(50,50,400,300).attr({stroke: '#3333FF'}),
transform,
// EDITED
xBound = {min: 50 + imgWidth/2, max: 450 - imgWidth/2},
yBound = {min: 50 + imgHeight/2, max: 350 - imgHeight/2};
start = function (x, y) {
// Find min and max values of dx and dy for "html5" element and store them for validating dx and dy in move()
// This is required to impose a rectagular bound on drag movement of "html5" element.
transform = html5.transform();
}
move = function (dx, dy, x, y) {
// To restrict movement of the dragged element, Validate dx and dy before applying below.
// Here, dx and dy are shifts along x and y axes, with respect to drag start position.
// EDITED
var deltaX = x > xBound.max && xBound.max - x || x < xBound.min && xBound.min - x || 0;
deltaY = y > yBound.max && yBound.max - y || y < yBound.min && yBound.min - y || 0;
this.attr({transform: transform + 'T'+ [dx + deltaX, dy + deltaY]});
}
up = function () {
};
html5.drag(move, start, up);
elementClockwise.click(function() {
html5.animate({transform: '...r90'}, 100);
})
elementCounterClockwise.click(function() {
html5.animate({transform: '...r-90'}, 100);
})
Use of '...' to append a transformation to the pre-existing transformation state (Raphael API) is important for the rotational issue. While, for translating the element on drag requires absolute translation, which neglects the rotational state of the element while translating the element.
//EDIT NOTE
Drag bounding is worked on and updated. However, there remains an issue with incorporating the difference between mouse position and image center.
I can help you with your rotation and drag problem, you need to store the rotation and apply it after you have moved the object.
elementClockwise.node.onclick = function()
{
html5.animate({'transform': html5.transform() +'r90'}, 100, onAnimComplete);
}
elementCounterClockwise.node.onclick = function()
{
html5.animate({'transform': html5.transform() +'r-90'}, 100, onAnimComplete);
}
function onAnimComplete(){
default_transform = html5.transform();
}
At present I can't get the boundary to work, but will have a try later.
http://jsfiddle.net/n3Sn5/2/
I have a HTML5 canvas on which I have drawn several shapes.
What I want to happen, is that when the mouse is clicked on any shape the shape should get selected (at least it can tell what kind of shape is selected).
Thank you.
Try using an existing canvas library (or create your own) that has an event when a shape is being selected.
The example below uses the Kinetic JS library, and the example below is from HTML5 Canvas Region Events Example:
var triangle = new Kinetic.Shape(function(){
var context = this.getContext();
context.beginPath();
context.lineWidth = 4;
context.strokeStyle = "black";
context.fillStyle = "#00D2FF";
context.moveTo(120, 50);
context.lineTo(250, 80);
context.lineTo(150, 170);
context.closePath();
context.fill();
context.stroke();
});
triangle.on("mouseout", function(){
writeMessage(messageLayer, "Mouseout triangle");
});
triangle.on("mousemove", function(){
var mousePos = stage.getMousePosition();
var x = mousePos.x - 120;
var y = mousePos.y - 50;
writeMessage(messageLayer, "x: " + x + ", y: " + y);
});
shapesLayer.add(triangle);
var circle = new Kinetic.Shape(function(){
var canvas = this.getCanvas();
var context = this.getContext();
context.beginPath();
context.arc(380, canvas.height / 2, 70, 0, Math.PI * 2, true);
context.fillStyle = "red";
context.fill();
context.lineWidth = 4;
context.stroke();
});
circle.on("mouseover", function(){
writeMessage(messageLayer, "Mouseover circle");
});
circle.on("mouseout", function(){
writeMessage(messageLayer, "Mouseout circle");
});
circle.on("mousedown", function(){
writeMessage(messageLayer, "Mousedown circle");
});
circle.on("mouseup", function(){
writeMessage(messageLayer, "Mouseup circle");
});
shapesLayer.add(circle);
stage.add(shapesLayer);
stage.add(messageLayer);
In addition, I have included some mouse-in detection if the cursor is within the shape, without using any javascript libraries.
Rectangular-based mouse-in detection:
function isCursorWithinRectangle(x, y, width, height, mouseX, mouseY) {
if(mouseX > x && mouseX < x + width && mouseY > y && mouseY < y + height) {
return true;
}
return false;
}
Circle-based mouse-in detection:
function isCursorWithinCircle(x, y, r, mouseX, mouseY) {
var distSqr = Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2);
if(distSqr < r * r) {
return true;
}
return false;
}
There's a very simple way to select complex shapes with pixel precision that doesn't involve bounding rectangles or math calculations.
The idea is that you duplicate all of your shapes onto a hidden secondary canvas, where you assign each shape a unique color. When you perform a mouseover or click event on the original canvas, you save the mouse's (x, y) coordinates in relation to the visible canvas, and then you look-up the pixel color on your hidden canvas using those same coordinates. Because each shape has a unique color on the hidden canvas, that color corresponds to the exact shape that the user selected.
Note that this only supports up to roughly 16.7 million shapes because RGB only has 24 bits of color, but that should be more than enough.
Here's a simple example using D3 and Canvas: http://bl.ocks.org/syntagmatic/6645345
Canvas does not have an interface for the elements on it like the DOM. It is solely used for drawing.
You need to create your assets as objects and use a drawing loop to paint them. You then forget about the canvas element, you work with your objects, with their offsets, etc.