Search code examples
colorschartsjqplotdiagram

How to change a color of one particular bar using jqplot and stacked bar chart


I got one straight forward question. Is it possible to change the color of one bar in stacked bar chart in any way (using jqplot options or relying on a hack)?

I have this:

enter image description here

I want this:

enter image description here

So as you can already assume I am using 3 different colors for the stacked bar chart:

seriesColors: ['#afafaf', '#c4c6c4', '#dbdcdd']

Problem is that I want to add one specific color for 1 particular bar.

Here is the JS code:

$(document).ready(
    function() {

        var el = [ 3, 6, 0, 10, 12 ];
        var ael = [ 14, 5, 0, 4, 2 ];
        var ipv = [ 4, 9, 0, 8, 4 ];
        var months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May' ];
        var colors = ['blue', 'red', 'white'];

        plot3 = $.jqplot('elDiagram', [ el, ael, ipv ], {
            stackSeries : true,
            seriesColors: colors,
            captureRightClick : true,
            seriesDefaults : {
                renderer : $.jqplot.BarRenderer,
                rendererOptions : {
                    barMargin : 30,
                    varyBarColor : true,
                    highlightMouseDown : true,
                    barWidth: 60
                },
                pointLabels : {
                    show : true
                }
            },
            axes : {
                xaxis : {
                    renderer : $.jqplot.CategoryAxisRenderer,
                    ticks : months,
                    tickOptions : {
                        mark : 'outside'
                    }
                },
                yaxis : {
                    tickOptions : {
                        show : false
                    },
                    padMin : 0
                }
            },
            series : [ {
                label : 'bla1'
            }, {
                label : 'bla2'
            }, {
                label : 'bla3'
            } ],
            legend : {
                show : true,
                location : 'ne',
                placement : 'inside'
            }
        });     
    });

Thanks!


Solution

  • OK, the solution was a hack which I will show and describe here:

    1. You need to overwrite the function called $.jqplot.BarRenderer.prototype.draw and change some of the lines
    2. You need to overwrite the function called getStart(sidx, didx, comp, plot, axis)
    3. You need to overwrite the function called $.jqplot.ShapeRenderer.prototype.draw and change some of the lines

    1.:

    $.jqplot.BarRenderer.prototype.draw = function(ctx, gridData, options, plot) {
       var i;
       // Ughhh, have to make a copy of options b/c it may be
       // modified later.
       var opts = $.extend({}, options);
    
       .................................
       <other code>
       .................................
    
       var clr = opts.fillStyle || this.color;
       this._dataColors.push(clr);
       this.renderer.shapeRenderer.draw(ctx, points, opts, i, pos); // changed line
    

    I changed the line in that way that I added i and pos parameters into the function. The reason was to indicate the current bar and position in the bar.

    2.:

    function getStart(sidx, didx, comp, plot, axis) {
       // check if sign change
       var seriesIndex = sidx, prevSeriesIndex = sidx - 1, start, prevVal, aidx = (axis === 'x') ? 0 : 1;
       // is this not the first series?
       if (seriesIndex > 0) {
           prevVal = plot.series[prevSeriesIndex]._plotData[didx][aidx];
           // is there a sign change
           if ((comp * prevVal) < 0) {
               start = getStart(prevSeriesIndex, didx, comp, plot, axis);
           }
           // no sign change.
           else {
               start = plot.series[prevSeriesIndex].gridData[didx][aidx];
           }
       }
       // if first series, return value at 0
       else {
           start = (aidx === 0) ? plot.series[seriesIndex]._xaxis.series_u2p(0) : plot.series[seriesIndex]._yaxis.series_u2p(0);
       }
       return start;
    }
    

    Nothing is changed here. You just need to copy the function because your new overwritten function cannot use it from jQPlot library.

    3.:

    $.jqplot.ShapeRenderer.prototype.draw = function(ctx, points, options, currentBar, position) {
        ctx.save();
        var opts = (options != null) ? options : {};
        var fill = (opts.fill != null) ? opts.fill : this.fill;
        var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
        var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
        var strokeRect = (opts.strokeRect != null) ? opts.strokeRect
                : this.strokeRect;
        var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect;
        var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
        var linePattern = (opts.linePattern != null) ? opts.linePattern
                : this.linePattern;
        var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
        ctx.lineWidth = opts.lineWidth || this.lineWidth;
        ctx.lineJoin = opts.lineJoin || this.lineJoin;
        ctx.lineCap = opts.lineCap || this.lineCap;
        ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle;
        ctx.fillStyle = opts.fillStyle || this.fillStyle;
        if (currentBar == activeColumn && position == 0) { // adding different color for the specific bar
            ctx.fillStyle = defaultColors[0];
        } else if (currentBar == activeColumn && position == 1) {
            ctx.fillStyle = defaultColors[1];
        } else if (currentBar == activeColumn && position == 2) {
            ctx.fillStyle = defaultColors[2];
        }
        ctx.beginPath();
        if (isarc) {
            ctx.arc(points[0], points[1], points[2], points[3], points[4], true);
            if (closePath) {
                ctx.closePath();
            }
            if (fill) {
                ctx.fill();
            } else {
                ctx.stroke();
            }
            ctx.restore();
            return;
        } else if (clearRect) {
            ctx.clearRect(points[0], points[1], points[2], points[3]);
            ctx.restore();
            return;
        } else if (fillRect || strokeRect) {
            if (fillRect) {
                ctx.fillRect(points[0], points[1], points[2], points[3]);
            }
            if (strokeRect) {
                ctx.strokeRect(points[0], points[1], points[2], points[3]);
                ctx.restore();
                return;
            }
        } else if (points && points.length) {
            var move = true;
            for ( var i = 0; i < points.length; i++) {
                // skip to the first non-null point and move to it.
                if (points[i][0] != null && points[i][1] != null) {
                    if (move) {
                        ctxPattern.moveTo(points[i][0], points[i][1]);
                        move = false;
                    } else {
                        ctxPattern.lineTo(points[i][0], points[i][1]);
                    }
                } else {
                    move = true;
                }
            }
            if (closePath) {
                ctxPattern.closePath();
            }
            if (fill) {
                ctx.fill();
            } else {
                ctx.stroke();
            }
        }
        ctx.restore();
    };
    

    Here you need to check whether the bar your currently at, is the default one. The important part of the code is:

    if (currentBar == activeColumn && position == 0) { // adding different color for the specific bar
        ctx.fillStyle = defaultColors[0];
    } else if (currentBar == activeColumn && position == 1) {
        ctx.fillStyle = defaultColors[1];
    } else if (currentBar == activeColumn && position == 2) {
        ctx.fillStyle = defaultColors[2];
    }
    

    I added 3 different colors for that bar just to have "more fancy" diagram :)