Reputation: 2906
So, I want to create a finite state machine-visualizer/editor with the help of kineticjs and i'm stumbling with the following scenario:
I have two "nodes", let's say circle-objects (grouped with a label) which are draggable on my stage. Now I wan't to click on one circle, hold the mouse and move it and add a connection (an arrow, for simplicities sake) between the two shapes.
So it would be great to have any hints on how to accomplish this for I haven't found a solution yet.
To specify it: The nodes themselves should stay draggable. My thought was: Add a black circle and a white circle with a slightly smaller radius, group them. then on dragstart white circle -> drag node, on dragstart black circle -> draw arrow.
The Problem is how to draw an arrow starting from one shape and following the mouse to it's target (which can be another nodegroup => connection to this group or a blank point of the stage => an overlay opens which lets the user choose another node to draw or cancel the drawing).
I hope this is somewhat clear to understand. For more information please feel free to ask me.
Best regards, Dominik
p.s.: The behaviour seems to be exactly like the behaviour lucidchart (dot com) uses when creating diagrams, so maybe you understand what I want to achieve better looking at their demo here: https://www.lucidchart.com/demo .
Upvotes: 2
Views: 2599
Reputation: 3160
First off, for simplicity's sake here is a fiddle on how to draw a basic Line with your mouse and KineticJS: http://jsfiddle.net/projeqht/fF3hh/
Let's say you already have two circles on the stage, and you need to draw a line to connect them.
We can use e.targetNode
to select the nodes on each event (mousedown, mouseup), for example:
layer.on("mousedown", function (e) {
var nodeDown = e.targetNode;
}
layer.on("mouseup", function (e) {
var nodeUp = e.targetNode;
}
We need to check if the parent of nodeDown
is a Kinetic.Group or something else.
nodeDown
has a Kinetic.Group for a parent, we can use this Group to store the new line, and the 2nd target node nodeUp
.nodeUp
does not have a Kinetic.Group for a parent, we need to see if nodeUp
has a Group for a parent. If nodeUp
has a Kinetic.Group for a parent, then we can use that Group to store the new line, and the first target node nodeDown
.nodeDown
or nodeUp
have a group for a parent, then we will need to create a new group for them and add all 3 shapes (2 circles and a line) to that new group.Use this tutorial to learn how to move shapes from 1 group to another: http://www.html5canvastutorials.com/kineticjs/html5-canvas-move-shape-to-another-container-with-kineticjs/
Also, if you move a shape from one group to another, you may want to remove()
or destroy()
the extra group if it is no longer needed.
While drawing a Line, you will have to disable dragging the shapes, so that you can drag and draw with a mouse. You can do that by doing something similar to this:
function stopDrag() {
for (var i=0; i<layer.children.length; i++) {
layer.children[i].setDraggable(false);
}
}
function startDrag() {
for (var i=0; i<layer.children.length; i++) {
layer.children[i].setDraggable(true);
}
}
This will make all the children of layer draggable and undraggable, but you might want to limit that by being more specific than select layer.children
. A nice trick I liked to use here was to name all groups that were draggable as "draggable_shapes" and then use var draggableArray = stage.get('.draggable_shapes')
to select all the groups that are allowed to be dragged, then you could loop through that array and setDraggable()
.
Another point to note is that the X and Y coordinates of the Line will be a bit tricky to calculate, depending on if it has a Group as a parent or a Layer. If the Line is grouped, line's coordinates will be relative to the Group position, or else the Line's coordinates will be relative to the Stage (top left corner).
This will get you started on connecting a line with two different circles. It's up to you to do the coordinate logic if you want the lines to only connect on the outer rim of the circles.
Maybe you might want to add a transparent rectangle (attribute opacity: 0
) behind each circle, so that on mousedown with the rectangle, you will call drawLine()
to start drawing a line. Or else if the user clicks the circle, it will drag the group. At least that has similar functionality to the lucid charts application.
Custom Hit Function (http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-custom-hit-function-tutorial/) would probably be a cleaner way to do this but I'm not 100% on using Custom Hit Functions, someone else might know better.
Let me know if you need further help. Good luck!
Upvotes: 3