Drawing Lines In HTML Without Canvas

Using Bresenham's Line Drawing Algorithm in JavaScript To Draw A Line Comprsed Entirely Out Of Standard HTML Elements (DIV, that is.)

Still available – Free CSS Book giveaway – $34.99 value, yours free for a retweet.

Do you ever have a project you lay off for later? You know, that one idea you wish you have coded but you can't because you are currently in the middle of a more important thing?

Well, drawing a line in HTML without canvas was something on my mind for over a year.

How would something like this work? I created this demo area to show the basic idea in action. Using your mouse move the line below to see how - given any two end points - we can render a line on a raster display. In this case represented by a simple HTML grid made out of DIV elements!

How Does It Work?

We'll use Bresenham's line algorithm. It solves the problem of drawing a line in Quadrant 1:

bresenham's algorithm is for quadrant 1 only, the rest still need to be implemented for each case, separately.

Drawing a line in any direction requires thinking in quadrants. While the algorithm for Quadrant 1 is simple, implementating for all quadrants (and octants) is tricky. However, it's really just a mirror image of the two octants in Quadrant 1.

Why Draw Lines in HTML Without Canvas?

Many reasons! To provide a clear explanation of how Bresenham's drawing algorithm works.

Many Bresenham's line drawing tutorials I came across only explained mathematical derivations. But they didn't go into much detail about code implementation for each octant. It just felt like implementing something you don't really understand.

It's simple – some things are just fun. I mean, wouldn't it be great to draw lines in HTML without using canvas or WebGL? In this line drawing tutorial I will write a JavaScript class that handles instantiation of a single HTML line. You can spawn as many as you like.

I come from game development background. And in games you often deal with drawing lines. But beyond that, I always found the idea of drawing lines in HTML fascinating. Probably because I've never seen it done before.

Obviously raw HTML rendering speed will be an issue. And yes, further optimizations will be required to achieve meaningful performance. But the focus of this line drawing tutorial is primarily set on the line drawing algorithm explanation itself. You can always re-implement it in any other raster environment or graphics library.

Implementing Bresenham's Line Drawing Algorithm in JS

One of the most popular algorithms is perhaps the Bresenham's line drawing algorithm. It's pretty simple and straightforward. We take two deltas and calculate the slope of the line equation, based on two ending points of the line.

The starting and ending points of the line are provided to the four parameters of the draw_line function ([x0, y0] and [x1, y1] respectively).

You can implement this in C, C++, Python, Java or any other language. In this tutorial, of course, we will use JavaScript.

Here is the pseudo code:

draw_line(x0, y0, x1, y1)

  // Calculate "deltas" of the line (difference between two ending points)
  dx = x1 - x0
  dy = y1 - y0

  // Calculate the line equation based on deltas
  D = (2 * dy) - dx
  y = y0

  // Draw the line based on arguments provided
  for x from x0 to x1

    // Draw pixel at this location
    pixel(x, y)

    // Progress the line drawing algorithm parameters
    if D > 0
       y = y + 1
       D = D - 2*dx
    end if
    D = D + 2*dy

Converting it to JavaScript, we get the following:

let draw_line = (x0, y0, x1, y1) => {

    // Calculate "deltas" of the line (difference between two ending points)
    let dx = x1 - x0;
    let dy = y1 - y0;

    // Calculate the line equation based on deltas
    let D = (2 * dy) - dx;

    let y = y0;

    // Draw the line based on arguments provided
    for (let x = x0; x < x1; x++)
    {
        // Place pixel on the raster display
        pixel(x, y);

        if (D >= 0)
        {
             y = y + 1;
             D = D - 2 * dx;
        }

        D = D + 2 * dy;
    }
};

First we calculate the difference between line's end points on both axis (dx and dy) which are usually referred to as the deltas – for each of the two dimensions respectively.

Then we calculate the delta of the line as a whole (here stored in variable D) – you can think of it as the mathematical equation for drawing a line. Or in other words the line slope equation.

Before iterating through each span of the line let's reset y-axis counter (that we will walk using a for-loop, as the line is being drawn) by setting it to the initial position on the line. Here y0 refers to the Y coordinate of the first point on the line.

Finally, draw the line one "pixel" at a time, in pseudo code shown as pixel(x, y) function. Once the pixel is rendered, we can adjust the current pixel position using the delta steps we calculated.

Every time the delta counter exceeds 0 we step down to the next pixel on the Y-axis and adjust the D variable again by in the opposite dimension, which ends up in progresively drawing the line across both dimensions until we reach line's end.

But that's not everything. In addition, depending on axis-dominance the for-loop must be modified to travel in that direction. To complete the algorithm, we must also adjust parameters based on what octant (out of eight) we are in. This is explained in detail in the following section.

Drawing In All 4 Quadrants / 8 Octants

The barebones Bresenham's line algorithm above is designed to draw a line only in one quadrant (Quadrant 1) of the Cartesian coordinate system. But we need to cover all directions. After all, a random line can be plotted from any point on the raster screen to any other point.

This means that in addition to the pseudo code above, we need to take care of two other things:

That's a lot of branching out. But the algorithm presented in the following example will take care of all possible cases.

The Complete Bresenham's Algorithm In JavaScript

Okay – all of this sounds great in theory – and we already covered the pseudo code and JavaScript version - but only for one quadrant! Let's take a look at the whole enchilada.

let draw_line = (x1, y1, x2, y2) => {

    // Iterators, counters required by algorithm
    let x, y, dx, dy, dx1, dy1, px, py, xe, ye, i;

    // Calculate line deltas
    dx = x2 - x1;
    dy = y2 - y1;

    // Create a positive copy of deltas (makes iterating easier)
    dx1 = Math.abs(dx);
    dy1 = Math.abs(dy);

    // Calculate error intervals for both axis
    px = 2 * dy1 - dx1;
    py = 2 * dx1 - dy1;

    // The line is X-axis dominant
    if (dy1 <= dx1) {

        // Line is drawn left to right
        if (dx >= 0) {
            x = x1; y = y1; xe = x2;
        } else { // Line is drawn right to left (swap ends)
            x = x2; y = y2; xe = x1;
        }

        pixel(x, y); // Draw first pixel

        // Rasterize the line
        for (i = 0; x < xe; i++) {
            x = x + 1;

            // Deal with octants...
            if (px < 0) {
                px = px + 2 * dy1;
            } else {
                if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) {
                    y = y + 1;
                } else {
                    y = y - 1;
                }
                px = px + 2 * (dy1 - dx1);
            }

            // Draw pixel from line span at currently rasterized position
            pixel(x, y);
        }

    } else { // The line is Y-axis dominant

        // Line is drawn bottom to top
        if (dy >= 0) {
            x = x1; y = y1; ye = y2;
        } else { // Line is drawn top to bottom
            x = x2; y = y2; ye = y1;
        }

        pixel(x, y); // Draw first pixel

        // Rasterize the line
        for (i = 0; y < ye; i++) {
            y = y + 1;

            // Deal with octants...
            if (py <= 0) {
                py = py + 2 * dx1;
            } else {
                if ((dx < 0 && dy<0) || (dx > 0 && dy > 0)) {
                    x = x + 1;
                } else {
                    x = x - 1;
                }
                py = py + 2 * (dx1 - dy1);
            }

            // Draw pixel from line span at currently rasterized position
            pixel(x, y);
        }
    }
 }

This is just one way of writing the Bresenham's line drawing algorithm. You can juggle around parameters and improvise on branching out. But the basic idea is there.

The main if-statement branches out between two potential axis-dominance cases.

The code is octant-aware – within the two if-statement scopes you will also see that the code basically mirrors itself between X and Y axis (tracked by variables px and py) but the algorithm logic is pretty much identical otherwise.

Final Words

Often, people ask me how to improve their coding skills. I usually suggest choosing a project slightly above the range of your current ability. If you make sure to finish it, this experience will help you advance your coding skills. For me, this was that type of project.

JavaScript Tutorials.

When I release something, I make sure it's worth reading or taking a look at — at least once.

javascript teacher on Twitter