Constraint movement to circle/ellipse/rectangle

Disclaimer - The following tutorial was created with a past version of JointJS. The tutorial is still provided for those who may face a similar problem, but it may no longer show the best practices of JointJS. You may encounter the following issues:

Our current recommendations on best practices can be found in the appropriate sections of the basic and intermediate tutorials.

Some applications might need to constrain an element dragging to ellipse, circle or even rectangle shapes. This post shows you how this can be achieved via a custom view for your element and with the help of the handy geometry library that is part of JointJS.

Creating a custom constraint view

First we need to create a custom view that overrides the pointerdown() and pointermove() methods. These methods make sure that the x and y coordinates passed to the default pointerdown() and pointermove() methods are located on the boundary of our ellipse object. To compute the points on the boundary of our ellipse, we simply take advantage of the ellipse.prototype.intersectionWithLineFromCenterToPoint() function. [A complete documentation to the geometry library of JointJS can be found on my blog]

// This is the ellipse that will be used as a constraint for our element dragging.
var constraint = g.ellipse(g.point(200, 150), 100, 80);

var ConstraintElementView = joint.dia.ElementView.extend({

    pointerdown: function(evt, x, y) {
        var position = this.model.get('position');
        var size = this.model.get('size');
        var center = g.rect(position.x, position.y, size.width, size.height).center();
        var intersection = constraint.intersectionWithLineFromCenterToPoint(center);
        joint.dia.ElementView.prototype.pointerdown.apply(this, [evt, intersection.x, intersection.y]);
    },
    pointermove: function(evt, x, y) {
        var intersection = constraint.intersectionWithLineFromCenterToPoint(g.point(x, y));
        joint.dia.ElementView.prototype.pointermove.apply(this, [evt, intersection.x, intersection.y]);
    }
});

Creating a paper and forcing it to use our custom view

Now we can just create a graph and paper as usual and tell the paper to use our custom view for all the element models. [Note that if you need a custom view for just one type of model (not all the models added to the paper), you can do that be defining a view for a specific type. An example of this can be found in the forum page.]

var namespace = joint.shapes;

var graph = new joint.dia.Graph({}, { cellNamespace: namespace });

var paper = new joint.dia.Paper({
    el: $('#paper'),
    width: 650,
    height: 400,
    gridSize: 1,
    model: graph,
    cellViewNamespace: namespace,
    elementView: ConstraintElementView
});

Finalizing the example by adding elements to the graph and drawing our constraint ellipse

We're almost there! Now we just add a circle element to the paper which will be the one whose dragging we just constraint. We also draw our ellipse so that it is visible in the paper. Here we'll use the built-in Vectorizer library that makes life easier when dealing with SVG.

var earth = new joint.shapes.basic.Circle({
    position: constraint.intersectionWithLineFromCenterToPoint(g.point(100, 100)).offset(-10, -10),
    size: { width: 20, height: 20 },
    attrs: { text: { text: 'earth' }, circle: { fill: '#2ECC71' } },
    name: 'earth'
});
graph.addCell(earth);

var orbit = V('<ellipse/>');
orbit.attr({
    cx: constraint.x, cy: constraint.y, rx: constraint.a, ry: constraint.b
});
V(paper.viewport).append(orbit);

That's it! One can use the exact same technique to constrain dragging to a rectangular area. The full source code to the demo is available here:

circle-constraint.js