How to get rounded corners on a Bar chart

This quick little HOWTO document demonstrates how you redefine the standard rect() function in order to achieve rounded tops to your bars on a Bar chart.

[No canvas support]

This might be a desirable effect for you. By default the canvas API doesn't have the facility to add rounded corners to a rectangle that it draws.

This though is easily added by redefining the rect() function to a customised function that provides the rounded corners.

This is what is being done here - the standard rect() function is redefined to a function of our own creating that gives the rectangle a rounded corner effect.


Include the RGraph library files

Here are the library files being included. Nothing different here - the bare minimum that you would need is the Bar chart library and the core library. The tooltips and dynamic libraries are only necessary if you want tooltips.

<script src="/libraries/combined.html/RGraph.common.core.js" ></script>
<script src="/libraries/combined.html/RGraph.common.dynamic.js" ></script> <!-- Just needed for the tooltips -->
<script src="/libraries/combined.html/RGraph.common.tooltips.js" ></script> <!-- Just needed for the tooltips -->
<script src="/libraries/combined.html/RGraph.bar.js" ></script>

Redefine the canvas 2D API rect() function

This is where the standard rect() function is redefined to a function that draws the rectangle how we want it to (with rounded corners at the top) instead of it being a bog-standard 90deg cornered rectangle.

The severity of the rounding effect is held in the global variable roundedCorners so it's easy for you to change this if you need to.

You could just as easily make this an extra argument to the function if you're going to be drawing lots of rectangles with different sized corners.

<script>
    // A global variable - this controls the extent of the rounding effect
    window.roundedCorners = 5;

    /**
    * This code redefines the standard canvas rect() function on the 2D drawing
    * context. The radius controls how severe the corners are rounded and it's
    * hard coded to be set to the roundedCorners global variable (ie it's on the
    * window object). Feel free to change that though and make it an optional
    * argument - it will be quite easy to do.
    * 
    * @param number x      The X coordinate
    * @param number y      The Y coordinate
    * @param number width  The width of the rectangle
    * @param number height The height of the rectangle
    */
    CanvasRenderingContext2D.prototype.rect = function (x, y, width, height)
    {
        // Get the extent of the rounding effect from the global variable
        var radius = window.roundedCorners;

        // Because the function is added to the 2D context prototype
        // the 'this' variable is actually the context.
        
        // Save the existing state of the canvas so that it can be restored later
        this.save();
        
            // Translate to the given X/Y coordinates at which we are to draw
            // the rectangle
            this.translate(x, y);

            // Move to the center of the top horizontal line of the rectangle
            this.moveTo(width / 2,0);
            
            // Draw the rounded corners. The connecting lines in between them are drawn automatically
            this.arcTo(width,0,width,height, Math.min(height / 2, radius));
            this.arcTo(width, height, 0, height, Math.min(width / 2, 0)); // The zero radius here means the rectangle has a flat bottom
            this.arcTo(0, height, 0, 0, Math.min(height / 2, 0)); // The zero radius here means the rectangle has a flat bottom
            this.arcTo(0, 0, radius, 0, Math.min(width / 2, radius));

            // Draw a line back to the start coordinates which effectively closes the rectangle
            this.lineTo(width / 2,0);

        // Restore the state of the canvas to as it was before the save()
        this.restore();
    }
</script>

The RGraph code to make the Bar chart

And here's the code that makes the Bar chart. It's just standard RGraph code with nothing special. Because the rect() function was redefined at the canvas API level the RGraph Bar chart is effectively 'unaware' of that - and uses the rect() function as it would normally.

<script>
    // Here is a regular RGraph Bar chart that will now make use of the above
    // rect function. Nothing has to be altered here because the native rect()
    // function was redefined above - so the Bar chart effectively doesn't think
    // anything is different.
    new RGraph.Bar({
        id: 'cvs',
        data: [12,13,16,15,16,19,19,12,23,16,13,24],
        options: {
            gutterTop: 35,
            gutterLeft: 35,
            shadow: true,
            shadowOffsetx: 2,
            shadowOffsety: 2,
            shadowBlur: 2,
            title: 'A Bar chart with rounded corners',
            labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            tooltips: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
            backgroundGridVlines: false,
            backgroundGridBorder: false,
            noaxes: true,
            colors: ['Gradient(#fcc:red)']
        }
    }).wave();
</script>

A downloadable demo

There's a demo available in the download archive that demonstrates this technique and all of the necessary code is easily accessible. Here's the demo:

Using the .grow() animation effect

[No canvas support]

The rounded corners effect shown here works perfectly well with the .wave() effect however the grow() effect does nor like it it seems.

Here's a demonstration of what happens when you try to use rounded corners and the grow() effect together (click the Animate button to see the effect). I've increased the number of frames on the animation - effectively slowing it down - so that you can see what's happening.

Is there anything that can be done about it?

[No canvas support]

Yes! Fortunately canvas has provisions that enables us to restrict the drawing area to the area confined by the gutters - ie the chart area.

Normally this would mean problems for drawing the labels - ie they wouldn't then be visible.

However since RGraph uses the textAccessible option by default then this is not a problem. This is because the text that RGraph canvas charts use is DOM based text and therefore not constrained by the canvas based clipping region.

This is a version of the chart when it's using the clipping region. Again, like the chart above, it has been slowed down so that you can see the difference.

<script>
    bar_grow2  = new RGraph.Bar({
        id: 'cvs3',
        data: [12,13,16,15,16,19,19,12,23,16,13,24],
        options: {
            gutterTop: 35,
            gutterLeft: 35,
            title: 'A demonstration of the grow() effect when using clipping',
            labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            tooltips: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
            backgroundGridVlines: false,
            backgroundGridBorder: false,
            noaxes: true,
            colors: ['red']
        }
    }).draw();
    
    // Create the clipping region. This can be done after the
    // initial draw() because the problem is only evident when
    // the grow() effect is used.
    var context = bar_grow2.context;

    context.beginPath();
    context.rect(
        bar_grow2.gutterLeft,
        bar_grow2.gutterTop,
        bar_grow2.canvas.width - bar_grow2.gutterLeft - bar_grow2.gutterRight,
        bar_grow2.canvas.height - bar_grow2.gutterTop - bar_grow2.gutterBottom
    );
    context.clip();

</script>