I want to create an application that the user will have the ability to click on a picture add points and for each (certain) pairs of points the software will draw a line between those two. The points are draggable so the line must be able to readjust itself to the new position of its two ancors. The points are pre-specified and more than 5 or 6 (it can be 10 or more)
So my design so far.
One kinetic stage
One backgroundLayer which will have the following children:
backgournImage (will hodl the image to click on)
The points aded
the lines between the points
I guess for each of the lines I will have to use a group that will contain the two ancors and the line. My problem is the following: Is there a convenient way to make sure a group is allready created for a certain pair of points so as not to create a new one when clicking on picture to add a new point?
stage.('contentClick', function(event){
//create a new point
// if a group for the specific pair of ancors exists
//add the point and draw the line
// else this is the first point of the pair we are talking about
// so create the group
//and add the new point
// add the group on the backgroundLayer
//redraw stage
);
All points can have a specific id the name of the point.
I know the code for creating points groups adding them removing them checking parents etc, just don't know how could I do it with the least manual method. I mean it's not very productive checking each and every one of the points right?
Hope I am making some sense...
One way is to track pairs of anchors that must be connected & reset their connecting lines when an anchor is dragged:
Create a Line to act as a connector.
Create a js object that holds references to the 2 anchors and the line connecting them.
Add the connection-object in an array.
When any anchor is dragged, iterate the array & reset the connectors for the anchor that was dragged.
Example code and a Demo: http://jsfiddle.net/m1erickson/b7h5dfg5/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.1.min.js"></script>
<style>
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:350px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var layer = new Kinetic.Layer();
stage.add(layer);
var connectors=[];
var a1=addAnchor(50,50,'one');
var a2=addAnchor(150,50,'two');
var a3=addAnchor(100,100,'three');
addConnection(a1,a3);
addConnection(a2,a3);
function resetConnections(id){
for(var i=0;i<connectors.length;i++){
var c=connectors[i];
if(c.id1==id || c.id2==id){
c.line.points([
c.anchor1.x(),
c.anchor1.y(),
c.anchor2.x(),
c.anchor2.y()
]);
}
}
layer.draw();
}
function addAnchor(x,y,id){
var anchor=new Kinetic.Circle({
id:id,
x:x,
y:y,
radius: 10,
fill: 'red',
stroke: 'black',
strokeWidth:2,
draggable: true
});
anchor.on('dragstart',function(){
stage.find(".connector").each(function(n){ n.hide(); });
layer.draw();
})
anchor.on('dragend',function(){
stage.find(".connector").each(function(n){ n.show(); });
resetConnections(this.id());
})
layer.add(anchor);
layer.draw();
return(anchor);
}
function addConnection(anchor1,anchor2){
var line=new Kinetic.Line({
name:'connector',
points:[anchor1.x(),anchor1.y(),anchor2.x(),anchor2.y()],
stroke:'black'
});
layer.add(line);
line.moveToBottom();
layer.draw();
connectors.push({
line:line,
id1:anchor1.id(),
id2:anchor2.id(),
anchor1:anchor1,
anchor2:anchor2
});
}
}); // end $(function(){});
</script>
</head>
<body>
<h4>Drag red anchors and connectors will follow</h4>
<div id="container"></div>
</body>
</html>
Related
I am developing hotel floor view using canvas with the help of kinetic js.I want to provide an option to delete current dragged,dropped image on canvas.
this is my code for only drag drop of image and not with delete option :
// get a reference to the house icon in the toolbar
// hide the icon until its image has loaded
var $house=$("#house");
$house.hide();
// get the offset position of the kinetic container
var $stageContainer=$("#container");
var stageOffset=$stageContainer.offset();
var offsetX=stageOffset.left;
var offsetY=stageOffset.top;
// create the Kinetic.Stage and layer
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var layer = new Kinetic.Layer();
stage.add(layer);
// start loading the image used in the draggable toolbar element
// this image will be used in a new Kinetic.Image
var image1=new Image();
image1.onload=function(){
$house.show();
}
image1.src="https://dl.dropboxusercontent.com/u/139992952/multple/4top.png";
// make the toolbar image draggable
$house.draggable({
helper:'clone',
});
// set the data payload
$house.data("url","house.png"); // key-value pair
$house.data("width","32"); // key-value pair
$house.data("height","33"); // key-value pair
$house.data("image",image1); // key-value pair
// make the Kinetic Container a dropzone
$stageContainer.droppable({
drop:dragDrop,
});
// handle a drop into the Kinetic container
function dragDrop(e,ui){
// get the drop point
var x=parseInt(ui.offset.left-offsetX);
var y=parseInt(ui.offset.top-offsetY);
// get the drop payload (here the payload is the image)
var element=ui.draggable;
var data=element.data("url");
var theImage=element.data("image");
// create a new Kinetic.Image at the drop point
// be sure to adjust for any border width (here border==1)
var image = new Kinetic.Image({
name:data,
x:x,
y:y,
image:theImage,
draggable: true
});
layer.add(image);
layer.draw();
}
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:350px;
}
#toolbar{
width:350px;
height:35px;
border:solid 1px blue;
}
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.2.min.js"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.min.js"></script>
<h4>Drag from toolbar onto canvas. Then drag around canvas.</h4>
<div id="toolbar">
<img id="house" width=32 height=32 src="https://dl.dropboxusercontent.com/u/139992952/multple/4top.png"><br>
</div>
<div id="container"></div>
You can use Kinetic to image.remove(), then layer.draw() to refresh the canvas, in similar fashion to this answer.
If you want to temporarily remove an image (and later re-add it) you use:
// temporarily remove the image from the layer
yourImage.remove();
yourLayer.draw();
// and later re-add it
yourLayer.add(yourImage);
yourLayer.draw();
But if you want to permanently delete the image (and recover all its resources) you use:
// permanently delete the image and reclaim all the resources it used
yourImage.destroy();
yourLayer.draw();
This is driving me crazy. If I run the simple HTML below it works on my IPad2 (A1396) but not on my IPad 3 (A1416). Whenever I rotate the IPad 3 from Portrait to Landscape Safari will crash. I looks like there is a relation between the number of layers added and the width/height (of each layer).
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.2.min.js"></script>
<script defer="defer">
var stage = new Kinetic.Stage({
container: 'container',
width: 580,
height: 400
});
var NrOfAttribute = 50;
for (var AttributeNo = 0, NrOfAttribute; AttributeNo < NrOfAttribute; AttributeNo++) {
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: 20*AttributeNo,
y: 20*AttributeNo,
width: 50,
height: 10,
fill: 'green',
stroke: 'black',
strokeWidth: 4
});
// add the shape to the layer
layer.add(rect);
// add the layer to the stage
stage.add(layer);
}
</script>
</body>
</html>
Is this a bug or some memory problem on the IPad? Any help would be appreciated because I really need this to work.
Thanks!
Edit: I just tested to do the same with "regular" Canvas and if I increase the number of layers it will also crash (so it doesn't seem to be Kinetic specific). It will also crash on Ipad2 if you increase the number of layers enough.
This might not be an option for you, but do you need a separate layer for each Rect, or could you put them all in the same layer? I suspect that would help reduce memory usage, as you'd wouldn't end up with 50 canvas elements. You could give each Rect a unique name and then still be able to manipulate them individually with something like rect = layer.find('.{name}'); # do something with rect
How can we bind (two-way) KineticJS Objects to some data with AngularJS ?
For example, bind a Kinetic Shape's position to a variable or textbox.
I've figured out how to do this without AngularJS :
(using KineticJS with jQuery)
box.on('dragmove', function() {
$('#pos_x').val( myShape.getX() );
$('#pos_y').val( myShape.getY() );
});
$('#pos_x').change(function() {
var x = parseInt( $('#pos_x').val() );
var y = parseInt( $('#pos_x').val() );
box.setPosition( x, y );
});
// and the same for $('#pos_y');
Code explanation:
There are a box and two textboxes.
When the box is dragged, box's coordinates will show on both textboxes.
When both textboxes' values are changed, the box's position will also change
but I wanted to know how to do that with AngularJS
(IMO, it will be much easier when you have a large number of objects, each with its own textboxes)
You have a few integration problems when trying to combine KineticJS with AngularJS
AngularJS is great at binding DOM elments.
But KineticJS objects are not DOM elements—they are just pixels on the canvas.
So AngularJS can’t control Kinetic objects directly.
To get Kinetic objects to move in response to text-input changes, you could use an AngularJS controller and make calls to the Kinetic object’s setX/setY.
To get text-input values to change as Kinetic objects are dragged, you might call the AngularJS controller from within a Kinetic dragmove event handler.
A complication is that by default, both Angular and Kinetic will want to control mouse events for their own purposes.
I'm not saying it can't be done, but...
Integrating KineticJS + AngularJS is more complicated than the Kinetic + jQuery method you’ve already got.
Before you give up on Kinetic + jQuery
Check out this code that integrates your Kinetic object and text-inputs.
You can quickly create as many shape+text pairs as you need.
And each pair is automatically integrated so that either dragging or text-input will move the shape and will show the current position in the text-input.
BTW, I used jQuery here for convenience, but you could very easily convert this to pure javascript and not need any external library at all.
Here’s code and a Fiddle: http://jsfiddle.net/m1erickson/X9QsU/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.0.min.js"></script>
<style>
body{background:ivory; padding:10px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:300px;
height:300px;
}
.boundXY{
width:105px;
height:23px;
color:white;
padding:5px;
border:2px solid lightgray;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 300,
height: 300
});
var layer = new Kinetic.Layer();
stage.add(layer);
// each rect,textX,textY gets a unique id
var nextId=0;
// create some rect-textInput pairs in random colors
for(var i=0;i<6;i++){
randomPair();
}
function randomPair(){
var x=parseInt(Math.random()*250);
var y=parseInt(Math.random()*250);
var w=parseInt(Math.random()*40)+10;
var h=parseInt(Math.random()*40)+10;
addRectTextPair(nextId++,x,y,w,h,randomColor(),"lightgray");
}
function addRectTextPair(id,x,y,w,h,fill,stroke){
// new kinetic rect
var rect = new Kinetic.Rect({
id:"rect"+id,
x: x,
y: y,
width: w,
height: h,
fill: fill,
stroke: stroke,
strokeWidth: 3,
draggable:true
});
rect.on('dragmove', function() {
var id=this.getId().slice(4);
$('#x'+id).val( parseInt(this.getX()) );
$('#y'+id).val( parseInt(this.getY()) );
});
layer.add(rect);
// new div with same color as kinetic rect
var div = document.createElement("div");
div.id="div"+id;
div.className="boundXY";
div.style.background = fill;
div.innerHTML = "X/Y:";
// add xy text inputs
div.appendChild(newTextInput("x"+id,x));
div.appendChild(newTextInput("y"+id,y));
// add div to body
document.body.appendChild(div);
// change rect's X when the textInputX changes
$('#x'+id).change(function(e) {
var id=e.target.id.slice(1);
var rect=layer.get("#rect"+id)[0];
rect.setX( parseInt($(this).val()) );
layer.draw();
});
// change rect's Y when the textInputY changes
$('#y'+id).change(function(e) {
var id=e.target.id.slice(1);
var rect=layer.get("#rect"+id)[0];
rect.setY( parseInt($(this).val()) );
layer.draw();
});
layer.draw();
}
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
function newTextInput(id,value){
var input=document.createElement("input");
input.id=id;
input.value=value;
input.type="text";
input.style.width="25px";
input.style.marginLeft="5px";
return(input);
}
$("#oneMore").click(function(){ randomPair(); });
}); // end $(function(){});
</script>
</head>
<body>
<p>Reposition rectangles by dragging or changing X/Y</p>
<button id="oneMore">Add another Rect and TextInput pair</button>
<div id="container"></div>
</body>
</html>
There is a tut on line drag and drop here :
http://www.html5canvastutorials.com/kineticjs/html5-canvas-drag-and-drop-a-line-with-kineticjs/
But more interestingly how to do drag and drop a line segment (fragment only) in kinetic JS ?
There's no example for doing this.
In my use case the segment stays attached to polyline, it just changes angle. so I don't want to create another polyline with one segment only which would also be a waste of resource.
How to drag one segment of a polyline with the other segments remaining connected
You can create a series of 2-point kinetic.lines (just a start and end point).
Each new start point is the end point of the previous line
This screenshot shows the green segment being dragged and the other segments changing accordingly.
Result: A polyline made up of draggable segments.
Note: after any segment is dragged, the getX()/getY() contain the distance dragged from its original XY.
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/GrEEL/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.5.5.min.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:300px;
height:300px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width:300,
height: 300
});
var layer = new Kinetic.Layer();
stage.add(layer);
var colors=['red','green','blue','orange'];
var lines=[];
var points=[];
points.push({x:125,y:50});
points.push({x:75,y:75});
points.push({x:200,y:200});
points.push({x:275,y:100});
for(var i=0;i<points.length-1;i++){
var p1=points[i];
var p2=points[i+1];
addSegment(i,p1.x,p1.y,p2.x,p2.y,colors[i]);
}
layer.draw();
function addSegment(i,x1,y1,x2,y2,color){
var line = new Kinetic.Line({
points: [x1,y1,x2,y2],
stroke:color,
strokeWidth: 25,
lineCap:"round",
lineJoin:"round",
draggable:true
});
line.index=i;
line.on("dragend",function(){
// get the amount of xy drag
var i=this.index;
var dx=this.getX();
var dy=this.getY();
// update the points array
var p0=points[i];
var p1=points[i+1];
p0.x+=dx;
p0.y+=dy;
p1.x+=dx;
p1.y+=dy;
// reset the dragged line
this.setPosition(0,0);
this.setPoints([p0.x,p0.y,p1.x,p1.y]);
layer.draw();
});
line.on("dragmove",function(){
// get the amount of xy drag
var i=this.index;
var dx=this.getX();
var dy=this.getY();
// adjust the ending position of the previous line
if(i>0){
var line=lines[i-1];
var pts=line.getPoints();
pts[1].x=points[i].x+dx;
pts[1].y=points[i].y+dy;
line.setPoints(pts);
}
// adjust the starting position of the next line
if(i<lines.length-1){
var line=lines[i+1];
var pts=line.getPoints();
pts[0].x=points[i+1].x+dx;
pts[0].y=points[i+1].y+dy;
line.setPoints(pts);
}
layer.draw();
});
layer.add(line);
lines.push(line);
}
}); // end $(function(){});
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
I am using FabricJS to develop a simple floor plan editor. When adding a Line (that should be a wall) to the canvas, I want the ability to control only the length of the line, not it's width-height.
I have tried setting lockScalingY: true but the problem is that when adding a line the control handles are so close to each other that I can't interact only with the horizontal control handles... the corner handles actually block the horizontal handles...
How can this be done?
Fine control of adjoining FabricJS lines
The way graphics programs like photoshop deal with this common problem is to let the user position lines near the desired intersection and then use the arrowkeys to "nudge" the line into place pixel one at a time.
The code below illustrates a rough solution that does the same thing for FabricJS lines:
User selects a line.
Turn off the control handles and borders.
Let the user "nudge" the line into perfect alignment (here the line length is increased).
Turn the the control handles and borders back On.
This is just "proof of concept" code. In your production app:
You'll probably want to use arrowkeys instead of buttons to "nudge".
I made nudging go 10px at a time--you might do this 1px at a time.
And when the user starts nudging, you'll automatically turn off the handles and borders for them.
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/dd742/
<!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>
<script type="text/javascript" src="fabric.min.js"></script>
<style>
body{ background-color: ivory; padding:30px; }
canvas{border: 1px solid red; }
</style>
<script>
$(function(){
var canvas = new fabric.Canvas('canvas');
var selectedLine;
var line1=newLine(50,100,150,100,"red");
var line2=newLine(160,50,160,150,"green");
canvas.add(line1,line2);
function newLine(x1,y1,x2,y2,color){
var line=new fabric.Line(
[x1,y1,x2,y2],
{fill:color,strokeWidth:15,selectable:true}
);
return(line);
};
$("#hOff").click(function(){
selectedLine.hasBorders=false;
selectedLine.hasControls=false;
canvas.renderAll();
});
$("#hOn").click(function(){
selectedLine.hasBorders=false;
selectedLine.hasControls=true;
canvas.renderAll();
});
$("#shorter").click(function(){
changeLineLength(selectedLine,-10);
canvas.renderAll();
});
$("#longer").click(function(){
changeLineLength(selectedLine,10);
canvas.renderAll();
});
function changeLineLength(line,adjustment){
if(!selectedLine){return;}
if(line.get("y1")==line.get("y2")){ // line is horizontal
line.set("x2",line.get("x2")+adjustment);
}else
if(line.get("x1")==line.get("x2")){ // line is vertical
line.set("y2",line.get("y2")+adjustment);
}else{ // line is diagonal
line.set("x2",line.get("x2")+adjustment);
line.set("y2",line.get("y2")+adjustment);
}
}
canvas.observe('object:selected', function(eventTarget) {
var event=eventTarget.e;
var target=eventTarget.target;
selectedLine=target;
});
}); // end $(function(){});
</script>
</head>
<body>
<p>Select a line,</p><br/>
<p>Turn handles Off</p><br/>
<p>Make lines intersect by pressing longer/shorter</p><br/>
<canvas id="canvas" width="600" height="200"></canvas>
<button id="hOff">Handles Off</button>
<button id="shorter">Shorter</button>
<button id="longer">Longer</button><br/>
<button id="hOn">Handles On</button>
</body>
</html>
May be helps somebody.
Add handler to event
'object:selected': _objSelected
Then at handler set unneeded control to false.
function _objSelected(e)
{
if(e.target.objectType == 'line')
{
var cv = e.target._controlsVisibility;
// mt - middle top, tl - top left and etc.
cv.mt = cv.mb = cv.tl = cv.bl = cv.tr = cv.br = false;
}
}
Also I added objectType (custom property) and padding (for beauty) to new object Line
var obj = new fabric.Line([50, 50, 150, 50], { padding: 10, objectType: 'line' });