Using AngularJS with KineticJS - javascript

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>

Related

Getting current cursor position on the clicked object in Fabric.js

I am developing a Web application that involves HTML canvas feature. I am using Fabric.JS - http://fabricjs.com/ to render objects on it. In Fabric.Js, I can implement event listeners to the object as in this link- http://fabricjs.com/.
I am now adding an image object to the canvas and implement the mouse event listener to that image object. When the image object on the canvas is clicked, I would like to retrieve the position (x, y) of the image clicked. But it is always returning zero.
I am adding image object to the canvas like this:
var imgInstance = new fabric.Image(js_left_temp_img, {
left: 0,
top: 0
});
imgInstance.scale(0.1)
imgInstance.selectable = false;
imgInstance.set('selectable', false)
$canvas.add(imgInstance);
Then I set up the event listener to the image object like this and try to retrieve the clicked position of image object like this.
imgInstance.on('mousedown', function(e){
//alert('Image clicked')
console.log(e.target.left + " - " + e.target.top)
});
When I click on the image, it fires the event, but the left and top values are always returning zero. How can I retrieve the current clicked left and top or x and y position of the image in the event listener?
Use object.getLocalPointer()
DEMO
var canvas = new fabric.Canvas('canvas'),imgInstance;
canvas.on('object:selected', function(e) {
});
fabric.Image.fromURL('http://fabricjs.com/assets/pug.jpg', function(img) {
img.set({
left: 0,
top: 0,
scaleX: 0.3,
scaleY: 0.3
});
canvas.add(img);
img.on('mousedown',function(options){
var pointer = this.getLocalPointer(options.e);
console.log(pointer);
})
});
canvas{
border:2px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id='canvas' width=500 height=400>

How to delete image(element) from canvas

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();

Issues in design/implementation of a software with kineticjs

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>

Canvas Clear in Paper.js

Does anyone know the best way to clear a canvas using paper.js
I tried the regular canvas clearing methods but they do not seem to work with paper.js
Html
<canvas id="myCanvas" style="background:url(images/graphpaper.jpg); background-repeat:no-repeat" height="400px" width="400px;"></canvas>
<button class="btn btn-default button" id="clearcanvas" onClick="clearcanvas();" type="button">Clear</button>
Javascript/Paperscript
<script type="text/paperscript" canvas="myCanvas">
// Create a Paper.js Path to draw a line into it:
tool.minDistance = 5;
var path;
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
function onMouseDown(event) {
// Create a new path and give it a stroke color:
path = new Path();
path.strokeColor = 'red';
path.strokeWidth= 3;
path.opacity="0.4";
// Add a segment to the path where
// you clicked:
path.add(event.point, event.point);
}
function onMouseDrag(event) {
// Every drag event, add a segment
// to the path at the position of the mouse:
path.add(event.point, event.point);
}
</script>
Regular clearing does not work because paper.js maintains a scene graph and takes care of drawing it for you. If you want to clear all items that you have created in a project, you need to delete the actual items:
project.activeLayer.removeChildren(); works as long as all your items are inside one layer. There is also project.clear() which removes everything, including the layer.
In case someone comes looking for an answer with little experience in Javascript, I made a simple example :
1) As Jürg Lehni mentioned project.activeLayer.removeChildren(); can be used to remove all items inside active layer.
2) To reflect the cleaning you have to call paper.view.draw();.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Circles</title>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="paper-full.js"></script>
<script type="text/paperscript" canvas="canvas">
function onMouseUp(event) {
var circle = new Path.Circle({
center: event.middlePoint,
radius: event.delta.length / 2
});
circle.strokeColor = 'black';
circle.fillColor = 'white';
}
</script>
<script type="text/javascript">
paper.install(window);
window.onload = function () {
paper.setup('canvas');
document.getElementById('reset').onclick = function(){
paper.project.activeLayer.removeChildren();
paper.view.draw();
}
};
</script>
</head>
<body>
<canvas style="background-color: #eeeeed" id="canvas" resize></canvas>
<input id="reset" type="button" value="Reset"/>
</body>
</html>
CSS :
html,
body {
margin: 0;
overflow: hidden;
height: 100%;
}
/* Scale canvas with resize attribute to full size */
canvas[resize] {
width: 58%;
height: 100%;
}
you can use project.activeLayer.removeChildren(); for clear entire layer,
or you can add your new paths to group and remove all childrens in group
var myPath;
var group = new Group();
function onMouseDown(event) {
myPath = new Path();
}
function onMouseDrag(event) {
myPath.add(event.point);
}
function onMouseUp(event) {
var myCircle = new Path.Circle({
center: event.point,
radius: 10
});
myCircle.strokeColor = 'black';
myCircle.fillColor = 'red';
group.addChild(myCircle);
}
// connect your undo function and button
document.getElementById('clearbutton').onclick = btnClear;
function btnClear(){
group.removeChildren();
}
c.width = c.width; ctx.clearRect(0,0,c.width,c.height); should be a good catchall if you've not tried it.
Beware however - setting canvas width will reset the canvas state including any applied transforms.
A quick google took me to https://groups.google.com/forum/#!topic/paperjs/orL7YwBdZq4 which specifies another (more paper.js specific) solution:
project.activeLayer.removeChildren();

FabricJS - add line to canvas and control only it's length

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' });

Categories

Resources