How to draw a line between 2 elements using JQuery and refreshing that line?

StuperUser picture StuperUser · Jan 17, 2011 · Viewed 41.1k times · Source

I am using JQuery-UI draggables and droppables to clone elements onto a div. What is the best way to draw a line between elements on a page using JQuery.

What is the best way to refresh lines on the page? I will have multiple lines on the page and only want to update a particular line rather than refresh every line.

Answer

StuperUser picture StuperUser · Feb 8, 2011

I now have this working.

In my experience, don't use jquery.svg, it may have been the pressure to solve it without learning my way around another plugin, but I found it more hassle than it was worth and caused compatibilty issues.

It's possible to solve using the HTML5 canvas and excanvas compatibility script, and a great html5 walkthrough, BUT because of how the HTML5 canvas works, it requires that all the linse on the canvas are destroyed and redrawn if a line needs to be removed or its position needs to be updated.

The elements that I draw lines between are inside a parent element that represents a relationship. The child elements represent a start and end, so I can redraw all of these relationships by getting a collection of the parents using e.g. $('.relationshipClass') and interrogating the set's elements' children to get the points of the line.
To use this code you will have to come up with an approach to easily get the line points available to redraw.

Markup:
Nice and simple, a html <div> to hold any elements that are drawn between (most probably JQuery UI draggables), and a <canvas> that will be in the same position

 <div id="divCanvasId" class="divCanvasClass"></div>
 <canvas id="html5CanvasId"></canvas>

CSS:
Don't control the <canvas> element's width with CSS, see Question on Canvas streching. Position the <canvas> in the same position as the <div> and behind it (with the z-index), this will show the lines up behind the <div> and prevent the <canvas> from blocking any drag and drop interactions with the <div>'s children.

canvas
{
    background-color: #FFFFFF;
    position: absolute;
    z-index: -10;
    /* control height and width in code to prevent stretching */
}

Javascript approach:
Create utility methods: the example code is inside a JQuery plugin that contains:

  • A helper function to reset the canvas (changing the width will delete all of
  • A helper function to draw lines between two elements
  • A function that draws lines between all the elements that require one

When you add a new line or adjust the position of a line, you destroy the existing lines and draw all of the lines. You can put the code below into conventional functions or leave as a plugin.

Javascript code:
N.B. not tested after anonymisation.

$(document).ready(function () {
    $.fn.yourExt = {

        _readjustHTML5CanvasHeight: function () {
            //clear the canvas by readjusting the width/height
            var html5Canvas = $('#html5CanvasId');
            var canvasDiv = $('#divCanvasId');

            if (html5Canvas.length > 0) {
                html5Canvas[0].width = canvasDiv.width();
                html5Canvas[0].height = canvasDiv.height();
            }
        }
        ,
        //uses HTML5 <canvas> to draw line representing relationship
        //IE support with excanvas.js
        _drawLineBetweenElements: function (sourceElement, targetElement) {

            //draw from/to the centre, not the top left
            //don't use .position()
            //that will be relative to the parent div and not the page
            var sourceX = sourceElement.offset().left + sourceElement.width() / 2;
            var sourceY = sourceElement.offset().top + sourceElement.height() / 2;

            var targetX = targetElement.offset().left + sourceElement.width() / 2;
            var targetY = targetElement.offset().top + sourceElement.height() / 2;

            var canvas = $('#html5CanvasId');

            //you need to draw relative to the canvas not the page
            var canvasOffsetX = canvas.offset().left;
            var canvasOffsetY = canvas.offset().top;

            var context = canvas[0].getContext('2d');

            //draw line
            context.beginPath();
            context.moveTo(sourceX - canvasOffsetX, sourceY - canvasOffsetY);
            context.lineTo(targetX - canvasOffsetX, targetY - canvasOffsetY);
            context.closePath();
            //ink line
            context.lineWidth = 2;
            context.strokeStyle = "#000"; //black
            context.stroke();
        }
        ,

        drawLines: function () {
        //reset the canvas
        $().yourExt._readjustHTML5CanvasHeight();

        var elementsToDrawLinesBetween;
        //you must create an object that holds the start and end of the line
        //and populate a collection of them to iterate through
        elementsToDrawLinesBetween.each(function (i, startEndPair) {
            //dot notation used, you will probably have a different method
            //to access these elements
            var start = startEndPair.start;
            var end = startEndPair.end;

            $().yourExt._drawLineBetweenElements(start, end);
        });
    }

You can now call these functions from event handlers (e.g. JQuery UI's drag event) to draw lines.