Skip to content

Instantly share code, notes, and snippets.

@gzoller
Created April 13, 2012 22:43
Show Gist options
  • Save gzoller/2380610 to your computer and use it in GitHub Desktop.
Save gzoller/2380610 to your computer and use it in GitHub Desktop.
Kinetic Plug-Ins
/**
* KineticJS Bezier Extension
* Compatible with KineticJS JavaScript Library v3.8.0
* Author: Greg Zoller
* Date: Apr 12 2012
*/
///////////////////////////////////////////////////////////////////////
// Bezier
///////////////////////////////////////////////////////////////////////
/**
* Bezier constructor. Bezier extends Line
* @constructor
* @param {Object} config
*/
Kinetic.Bezier = function (config) {
config.name = "Bezier";
this.controlPoint1 = config.controlPoint1;
this.controlPoint2 = config.controlPoint2;
this.hasArrow = (typeof config.hasArrow !== "undefined") ? config.hasArrow : false;
// call super constructor
config.drawFunc = this._drawCurve;
Kinetic.Line.apply(this, [config]);
this.classType = "Bezier";
};
/*
* Bezier methods
*/
Kinetic.Bezier.prototype = {
/**
* draws curve
*/
_drawCurve: function () {
var context = this.getContext();
context.save();
context.beginPath();
context.moveTo(this.startPoint.x,this.startPoint.y);
context.bezierCurveTo(this.controlPoint1.x,this.controlPoint1.y,
this.controlPoint2.x,this.controlPoint2.y,
this.endPoint.x,this.endPoint.y);
context.strokeStyle = this.color;
context.lineWidth = this.lineWidth;
if(this.hasArrow) {
context.stroke();
context.closePath();
context.beginPath();
var headlen = 13; // length of head in pixels
var angle = Math.atan2(this.endPoint.y-this.controlPoint2.y,this.endPoint.x-this.controlPoint2.x);
context.lineJoin = "round";
var ax = this.endPoint.x-headlen*Math.cos(angle-Math.PI/6);
var ay = this.endPoint.y-headlen*Math.sin(angle-Math.PI/6);
context.moveTo(this.endPoint.x, this.endPoint.y);
context.lineTo(this.endPoint.x-headlen*Math.cos(angle+Math.PI/6),this.endPoint.y-headlen*Math.sin(angle+Math.PI/6));
context.lineTo(ax,ay);
context.fillStyle = this.color;
context.fill();
}
context.stroke();
context.closePath();
context.restore();
},
/**
* move emtire curve and control points
* @param {object} {}
*/
transform: function (delta) {
this.startPoint.x += delta.dx;
this.startPoint.y += delta.dy;
this.endPoint.x += delta.dx;
this.endPoint.y += delta.dy;
this.controlPoint1.x += delta.dx;
this.controlPoint1.y += delta.dy;
this.controlPoint2.x += delta.dx;
this.controlPoint2.y += delta.dy;
}
};
// extend Line
Kinetic.GlobalObject.extend(Kinetic.Bezier, Kinetic.Line);
/**
* KineticJS GraphNode Extension
* Compatible with KineticJS JavaScript Library v3.8.0
* Author: Greg Zoller
* Date: Apr 12 2012
*/
///////////////////////////////////////////////////////////////////////
// GraphNode
///////////////////////////////////////////////////////////////////////
/**
* GraphNode constructor. GraphNode extends Group
* @constructor
* @param {Object} config
*/
Kinetic.GraphNode = function (config) {
this.vertices = new Array(); // array of GraphVertex
this.rect = config.rect;
this.lineProps = config.lineProps;
Kinetic.Group.apply(this, [config]);
this.classType = "GraphNode";
this.add(this.rect);
this.on("mouseover",function() { document.body.style.cursor="pointer"; });
this.on("mouseout",function() { document.body.style.cursor="default"; });
this.on("dragmove", function() { for(i=0;i<this.vertices.length;i++) { this.vertices[i]._dragUpdate(); } });
if(typeof config.onDropCB !== "undefined") { this.on("dragend", config.onDropCB); }
};
/*
* GraphNode methods
*/
Kinetic.GraphNode.prototype = {
connectTo : function( gn ) {
gv = new Kinetic.GraphVertex({start: this, end: gn, lineWidth: this.lineProps.lineWidth, color: this.lineProps.color});
this.getLayer().add(gv);
}
};
// extend Group
Kinetic.GlobalObject.extend(Kinetic.GraphNode, Kinetic.Group);
/**
* KineticJS GraphVertex Extension
* Compatible with KineticJS JavaScript Library v3.8.0
* Author: Greg Zoller
* Date: Apr 12 2012
*/
///////////////////////////////////////////////////////////////////////
// GraphVertex
///////////////////////////////////////////////////////////////////////
/**
* GraphVertex constructor. GraphVertex extends Group
* @constructor
* @param {Object} config
*/
Kinetic.GraphVertex = function (config) {
var r = config.start.rect;
this.start = config.start; // a GraphNode
this.end = config.end; // a GraphNode
this.line = (typeof config.end !== "undefined")
? new Kinetic.Line({ // draw line, we have start + end points
startPoint: this._getStartPt(config.start,config.end),
endPoint : this._getEndPt(config.start,config.end),
lineWidth : config.lineWidth,
color : config.color,
hasArrow : true
})
: new Kinetic.Bezier({ // draw curve, we have only start -- self-referencing vertex
startPoint : {x:r.attrs.x+r.getWidth()/2,y:r.attrs.y+r.getHeight()},
endPoint : {x:r.attrs.x+r.getWidth()/4,y:r.attrs.y+r.getHeight()},
controlPoint1 : {x:r.attrs.x+r.getWidth()/2,y:r.attrs.y+r.getHeight()+40},
controlPoint2 : {x:r.attrs.x+r.getWidth()/4,y:r.attrs.y+r.getHeight()+40},
lineWidth : config.lineWidth,
color : config.color,
hasArrow : true
});
Kinetic.Group.apply(this, [config]);
this.classType = "GraphVertex";
this.add(this.line);
this.start.vertices.push(this);
if( typeof config.end !== "undefined" ) this.end.vertices.push(this);
this.on("mouseover",function() { document.body.style.cursor="pointer"; });
this.on("mouseout",function() { document.body.style.cursor="default"; });};
/*
* GraphVertext Methods
*/
Kinetic.GraphVertex.prototype = {
_dragUpdate : function() {
if( this.line.classType == "Line" )
this.line.transform(this._getStartPt(this.start,this.end), this._getEndPt(this.start,this.end));
else { // move curve
var r = this.start.rect;
var dx = r.getAbsolutePosition().x - (this.line.startPoint.x - r.getWidth()/2);
var dy = r.getAbsolutePosition().y - (this.line.startPoint.y - r.getHeight());
this.line.transform({dx:dx,dy:dy});
}
},
_getStartPt : function( gn1, gn2 ) {
var r1 = gn1.rect;
var r2 = gn2.rect;
switch( this._getOrientation(r1,r2) ) {
case 1:
return {
x:r1.getAbsolutePosition().x + r1.getSize().width,
y:r1.getAbsolutePosition().y + r1.getSize().height/2
}
break;
case 2:
return {
x:r1.getAbsolutePosition().x,
y:r1.getAbsolutePosition().y + r1.getSize().height/2
}
break;
case 3:
return {
x:r1.getAbsolutePosition().x + r1.getSize().width/2,
y:r1.getAbsolutePosition().y + r1.getSize().height
}
break;
case 4:
return {
x:r1.getAbsolutePosition().x + r1.getSize().width/2,
y:r1.getAbsolutePosition().y
}
break;
}
},
_getEndPt : function( gn1, gn2 ) {
var r1 = gn1.children[0];
var r2 = gn2.children[0];
switch( this._getOrientation(r1,r2) ) {
case 1:
return {
x:r2.getAbsolutePosition().x,
y:r2.getAbsolutePosition().y + r2.getSize().height/2
}
break;
case 2:
return {
x:r2.getAbsolutePosition().x + r2.getSize().width,
y:r2.getAbsolutePosition().y + r2.getSize().height/2
}
break;
case 3:
return {
x:r2.getAbsolutePosition().x + r2.getSize().width/2,
y:r2.getAbsolutePosition().y
}
break;
case 4:
return {
x:r2.getAbsolutePosition().x + r2.getSize().width/2,
y:r2.getAbsolutePosition().y + r2.getSize().height
}
break;
}
},
_getOrientation : function(box1, box2 ) { // 1=left, 2=right, 3=above, 4=below
var pos1 = box1.getAbsolutePosition();
var pos2 = box2.getAbsolutePosition();
if( pos1.x + box1.getSize().width <= pos2.x ) { return 1; }
if( pos2.x + box2.getSize().width <= pos1.x ) { return 2 }
if( box1.getAbsolutePosition().y >= box2.getAbsolutePosition().y ) { return 4; }
return 3;
}
};
// extend Group
Kinetic.GlobalObject.extend(Kinetic.GraphVertex, Kinetic.Group);
/**
* KineticJS Line Extension
* Compatible with KineticJS JavaScript Library v3.8.0
* Author: Mateusz Sokoła, modified by Greg Zoller (arrow heads)
* Date: Mar 12 2012
*/
///////////////////////////////////////////////////////////////////////
// Line
///////////////////////////////////////////////////////////////////////
/**
* Line constructor. Line extends Shape
* @constructor
* @param {Object} config
*/
Kinetic.Line = function (config) {
if (this._validatePoint([config.startPoint, config.endPoint])) {
config.name = "Line";
this.startPoint = config.startPoint;
this.endPoint = config.endPoint;
this.lineWidth = (typeof config.lineWidth === "number") ? config.lineWidth : 5;
this.color = (typeof config.color !== "undefined") ? config.color : "black";
this.hasArrow = (typeof config.hasArrow !== "undefined") ? config.hasArrow : false;
// call super constructor
config.drawFunc = (typeof config.drawFunc === "undefined") ? this._drawLine : config.drawFunc;
Kinetic.Shape.apply(this, [config]);
this.classType = "Line";
}
};
/*
* Line methods
*/
Kinetic.Line.prototype = {
/**
* validates point correctness
* @param {array} args
*/
_validatePoint: function (args) {
var each = function (arr) {
for (var i = 0; i < arr.length; i++) {
if (typeof arr[i] === "object") {
if (typeof arr[i].x !== "number" || typeof arr[i].y !== "number") {
throw "Properties startPoint or/and endPoint does not contains properties x and y or these properties are not numeric";
return false;
}
} else {
throw "Invalid type of properties startPoint or/and endPoint or these properties are not set";
return false;
}
}
return true;
}
return each(args);
},
/**
* draws line
*/
_drawLine: function () {
var context = this.getContext();
context.save();
context.beginPath();
context.strokeStyle = this.color;
context.lineWidth = this.lineWidth;
context.moveTo(this.startPoint.x, this.startPoint.y);
context.lineTo(this.endPoint.x, this.endPoint.y);
if(this.hasArrow) {
context.stroke();
context.closePath();
context.beginPath();
var headlen = 13; // length of head in pixels
var angle = Math.atan2(this.endPoint.y-this.startPoint.y,this.endPoint.x-this.startPoint.x);
context.lineJoin = "round";
var ax = this.endPoint.x-headlen*Math.cos(angle-Math.PI/6);
var ay = this.endPoint.y-headlen*Math.sin(angle-Math.PI/6);
context.moveTo(this.endPoint.x, this.endPoint.y);
context.lineTo(this.endPoint.x-headlen*Math.cos(angle+Math.PI/6),this.endPoint.y-headlen*Math.sin(angle+Math.PI/6));
context.lineTo(ax,ay);
context.fillStyle = this.color;
context.fill();
}
context.stroke();
context.closePath();
context.restore();
},
/**
* transforms line
* @param {object} endPoint or startPoint
* @param {object} endPoint (optional)
*/
transform: function () {
if (arguments.length !== 0) {
if (this._validatePoint(arguments)) {
if (arguments.length > 1) {
this.startPoint = arguments[0];
this.endPoint = arguments[1];
} else {
this.endPoint = arguments[0];
}
this._drawLine();
this.getLayer().draw();
}
} else {
throw "Set at least one argument. Trigger: Kinetic.Line.transformLine";
return null;
}
},
};
// extend Shape
Kinetic.GlobalObject.extend(Kinetic.Line, Kinetic.Shape);
<html>
<head>
<script src="kinetic-v3.9.2.js"></script>
<script src="kinetic.line.js"></script>
<script src="kinetic.bezier.js"></script>
<script src="kinetic.graph.js"></script>
</head>
<body>
<script>
function drawBox(x,y,labelText) { // returns Group
var rect = new Kinetic.Rect({
x:x,
y:y,
width:100,
height:50,
fill:"#00D2FF",
stroke: "black",
strokeWidth: 4
});
var label = new Kinetic.Text({
x: x+20,
y: y+10,
text: labelText,
fontSize: 16,
fontFamily: "Calibri",
textFill:"green"
});
var group = new Kinetic.GraphNode({ rect:rect, draggable:true, lineProps: {lineWidth:3,color:"blue"} });
group.add(label);
return group;
}
window.onload = function() {
var stage = new Kinetic.Stage({
container: "area",
width: 780,
height: 350
});
var layer = new Kinetic.Layer();
var gn1 = drawBox(230,25,"One");
var gn2 = drawBox(230,135,"Two");
var gn3 = drawBox(400,75,"Three");
layer.add(gn1);
layer.add(gn2);
layer.add(gn3);
gn1.connectTo(gn2);
gn2.connectTo();
gn1.connectTo(gn3);
gn2.connectTo(gn3);
stage.add(layer);
};
</script>
<h2>Canvas Sample</h2>
<div id='area' style='border: 3px solid red'>
</div>
<body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment