Ben Ripkens

back to blog index

SVG path arc curve command

The basic SVG rect shape provides functionality for rounded corners. Unfortunately, you can only change it for all four corners. I needed a rectangle which supports something like the CSS3 top- and bottom-border-radius instructions.

#foo {
  border-top-left-radius: 10px;
  border-top-right-radius: 10px;
  border-bottom-right-radius: 20px;
  border-bottom-left-radius: 20px;
}

I needed this because of my GSoC project. In this project, there are different node types which have to be visualized. You can see the two node types (which are the reason for this blog post) in the following figure.

Two nodes, one of them only with two rounded corners.

Sticking to the Don’t-Repeat-Yourself paradigm, I abstracted the drawing of rectangular shapes into a base class RectangularNode from which the classes Macromolecule and NucleicAcidFeature inherit. Since the two node types are the same (with regard to implementation), except for the border radius, I put all the funtionality into the base class RectangularNode. Now the Macromolecule and NucleicAcidFeature classes are merely subclasses which set the border radius on the RectangularNode class.

Now to the drawing of rectangles with different border-radii, there is no basic SVG shape which supports such behavior. As a result, you need the SVG path element. The path element enables you to draw such things as curves and arcs and can be used to draw every possible shape. For this specific issue, the arc is needed. Please refer to the specification for an explanation of the various commands and the path element in general. In this post I’ll only cover the generation of rectangles with different border radii and the arc command. The arc command is defined the following way (expressed as a regular expression in JavaScript with an example parsing).

// please note that this regex doesn't take optional whitespace
// and commas into account.
var regex = /^(a|A)([+\-]?\d+),([+\-]?\d+) ([+\-]?\d+) (0|1),(0|1) ([+\-]?\d+),([+\-]?\d+)$/;

var command = "a10,10 0 0,1 150,100";

var match = command.match(regex);

var evaluatedCommand = {
    relative : match[1] === 'a',
    radiusX : parseInt(match[2]),
    radiusY : parseInt(match[3]),
    xAxisRotation : parseInt(match[4]),
    largeArc : match[5] === '1',
    sweep : match[6] === '1',
    targetX : parseInt(match[7]),
    targetY : parseInt(match[8])
};

console.log(evaluatedCommand);</pre> A very good description of the parameters can be found in the specification. Now to the actual generation, I’m uing the following two helper functions for this. Please refer to the comments in the source code for a description of the functionality.

// generate a path's arc data parameter
// http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
var arcParameter = function(rx, ry, xAxisRotation, largeArcFlag, sweepFlag,
                          x, y) {
    return [rx,
            ',',
            ry,
            ' ',
            xAxisRotation,
            ' ',
            largeArcFlag,
            ',',
            sweepFlag,
            ' ',
            x,
            ',',
            y].join('');
};

/*
 * Generate a path's data attribute
 *
 * @param {Number} width Width of the rectangular shape
 * @param {Number} height Height of the rectangular shape
 * @param {Number} tr Top border radius of the rectangular shape
 * @param {Number} br Bottom border radius of the rectangular shape
 * @return {String} a path's data attribute value
 */
var generatePathData = function(width, height, tr, br) {
    var data = [];

    // start point in top-middle of the rectangle
    data.push('M' + width / 2 + ',' + 0);

    // next we go to the right
    data.push('H' + (width - tr));

    if (tr &gt; 0) {
        // now we draw the arc in the top-right corner
        data.push('A' + arcParameter(tr, tr, 0, 0, 1, width, tr));
    }

    // next we go down
    data.push('V' + (height - br));

    if (br &gt; 0) {
        // now we draw the arc in the lower-right corner
        data.push('A' + arcParameter(br, br, 0, 0, 1, width - br,
                height));
    }

    // now we go to the left
    data.push('H' + br);

    if (br &gt; 0) {
        // now we draw the arc in the lower-left corner
        data.push('A' + arcParameter(br, br, 0, 0, 1, 0, height - br));
    }

    // next we go up
    data.push('V' + tr);

    if (tr &gt; 0) {
        // now we draw the arc in the top-left corner
        data.push('A' + arcParameter(tr, tr, 0, 0, 1, tr, 0));
    }

    // and we close the path
    data.push('Z');

    return data.join(' ');
};

With this utility function we can now generate rectangles in the following way (use this jsfiddle to try it right away) .

var svgns = 'http://www.w3.org/2000/svg';

var svg = document.getElementsByTagName('svg')[0];

var rect = document.createElementNS(svgns, 'path');
var pathDataValue = generatePathData(300, 200, 0, 20);
rect.setAttributeNS(null, 'd', pathDataValue);

svg.appendChild(rect);
That's me, Ben.
Hey, I am Ben Ripkens (bripkens) and this is my blog. I live in Düsseldorf (Germany) and I am employed by the codecentric AG as a Software Engineer. Web application frontends are my main area of expertise, but you may also find some other interesting articles on this blog.