// o---------------------------------------------------------------------------------o
    // | This file is part of the RGraph package - you can learn more at:                |
    // |                                                                                 |
    // |                       https://www.rgraph.net/license.html                       |
    // |                                                                                 |
    // | RGraph is dual-licensed under the Open Source GPL license. That means that it's |
    // | free to use and there are no restrictions on what you can use RGraph for!       |
    // | If the GPL license does not suit you however, then there's an inexpensive       |
    // | commercial license option available. See the URL above for more details.        |
    // o---------------------------------------------------------------------------------o


    RGraph = window.RGraph || {isrgraph:true,isRGraph: true,rgraph:true};
    RGraph.SVG = RGraph.SVG || {};

// Module pattern
(function (win, doc, undefined)
{
    RGraph.SVG.Waterfall = function (conf)
    {
        //
        // A setter that the constructor uses (at the end)
        // to set all of the properties
        //
        // @param string name  The name of the property to set
        // @param string value The value to set the property to
        //
        this.set = function (name, value)
        {
            // BC
            if (name === 'colorsConnector') {
                name = 'colorsConnectors';
            }

            if (arguments.length === 1 && typeof name === 'object') {
                for (i in arguments[0]) {
                    if (typeof i === 'string') {
                        this.set(i, arguments[0][i]);
                    }
                }
            } else {

                // Go through all of the properties and make sure
                // that they're using the correct capitalisation
                name = this.properties_lowercase_map[name.toLowerCase()] || name;

                var ret = RGraph.SVG.commonSetter({
                    object: this,
                    name:   name,
                    value:  value
                });
                
                name  = ret.name;
                value = ret.value;

                this.properties[name] = value;

                // If setting the colors, update the originalColors
                // property too
                if (name === 'colors') {
                    this.originalColors = RGraph.SVG.arrayClone(value, true);
                    this.colorsParsed = false;
                }
            }

            return this;
        };








        //
        // A getter.
        // 
        // @param name  string The name of the property to get
        //
        this.get = function (name)
        {
            // Go through all of the properties and make sure
            // that they're using the correct capitalisation
            name = this.properties_lowercase_map[name.toLowerCase()] || name;

            return this.properties[name];
        };





        // Convert strings to numbers
        conf.data = RGraph.SVG.stringsToNumbers(conf.data);



        this.type             = 'waterfall';
        this.id               = conf.id;
        this.uid              = RGraph.SVG.createUID();
        this.container        = document.getElementById(this.id);
        this.layers           = {}; // MUST be before the SVG tag is created!
        this.svg              = RGraph.SVG.createSVG({object: this,container: this.container});
        this.svgAllGroup      = RGraph.SVG.createAllGroup(this);
        this.clipid           = null; // Used to clip the canvas
        this.isRGraph         = true;
        this.isrgraph         = true;
        this.rgraph           = true;
        this.data             = RGraph.SVG.arrayClone(conf.data, true);
        this.coords           = [];
        this.coordsConnectors = [];
        this.colorsParsed     = false;
        this.originalColors   = {};
        this.gradientCounter  = 1;
        this.totalColumns     = [];
        this.firstDraw        = true; // After the first draw this will be false





        // Add this object to the ObjectRegistry
        RGraph.SVG.OR.add(this);
        
        this.container.style.display = 'inline-block';
        
        //
        // Note the indexes of total columns
        //
        for (var i=0; i<this.data.length; ++i) {
            if (RGraph.SVG.isNullish(this.data[i])) {
                this.totalColumns[i] = true;
            }
        }

        this.properties =
        {
            marginLeft:   35,
            marginRight:  35,
            marginTop:    35,
            marginBottom: 35,
            marginInner:  5,

            backgroundColor:            null,
            backgroundImage:            null,
            backgroundImageAspect:      'none',
            backgroundImageStretch:     true,
            backgroundImageOpacity:     null,
            backgroundImageX:           null,
            backgroundImageY:           null,
            backgroundImageW:           null,
            backgroundImageH:           null,
            backgroundGrid:             true,
            backgroundGridColor:        '#ddd',
            backgroundGridLinewidth:    1,
            backgroundGridHlines:       true,
            backgroundGridHlinesCount:  null,
            backgroundGridVlines:       true,
            backgroundGridVlinesCount:  null,
            backgroundGridBorder:       true,
            backgroundGridDashed:       false,
            backgroundGridDotted:       false,
            backgroundGridDashArray:    null,
            
            // 20 colors. If you need more you need to set the colors property
            colors:               ['black', 'red', 'blue'],
            colorsSequential:     false,
            colorsStroke:          '#aaa',
            colorsConnectors:      '#666',
            
            total:                true,
            linewidth:            1,

            yaxis:                true,
            yaxisLinewidth:       1,
            yaxisTickmarks:       true,
            yaxisTickmarksLength: 5,
            yaxisColor:           'black',
            yaxisScale:           true,
            yaxisLabels:          null,
            yaxisLabelsOffsetx:   0,
            yaxisLabelsOffsety:   0,
            yaxisLabelsCount:     5,
            yaxisScaleUnitsPre:        '',
            yaxisScaleUnitsPost:       '',
            yaxisScaleStrict:          false,
            yaxisScaleDecimals:        0,
            yaxisScalePoint:           '.',
            yaxisScaleThousand:        ',',
            yaxisScaleRound:           false,
            yaxisScaleMax:             null,
            yaxisScaleMin:             0,
            yaxisScaleFormatter:       null,
            yaxisLabelsColor:       null,
            yaxisLabelsBold:        null,
            yaxisLabelsItalic:      null,
            yaxisLabelsFont:        null,
            yaxisLabelsSize:        null,
            yaxisTitle:                '',
            yaxisTitleBold:            null,
            yaxisTitleSize:            null,
            yaxisTitleFont:            null,
            yaxisTitleColor:           null,
            yaxisTitleItalic:          null,
            yaxisTitleOffsetx:         0,
            yaxisTitleOffsety:         0,
            yaxisTitleX:               null,
            yaxisTitleY:               null,
            yaxisTitleHalign:          null,
            yaxisTitleValign:          null,

            xaxis:                true,
            xaxisLinewidth:       1,
            xaxisTickmarks:       true,
            xaxisTickmarksLength: 5,
            xaxisLabels:          null,
            xaxisLabelsFont:      null,
            xaxisLabelsSize:      null,
            xaxisLabelsColor:     null,
            xaxisLabelsBold:      null,
            xaxisLabelsItalic:    null,
            xaxisLabelsPosition:  'section',
            xaxisLabelsPositionEdgeTickmarksCount: null,
            xaxisLabelsFormattedDecimals:  0,
            xaxisLabelsFormattedPoint:     '.',
            xaxisLabelsFormattedThousand:  ',',
            xaxisLabelsFormattedUnitsPre:  '',
            xaxisLabelsFormattedUnitsPost: '',
            xaxisColor:           'black',
            xaxisLabelsOffsetx:   0,
            xaxisLabelsOffsety:   0,
            xaxisTitle:           '',
            xaxisTitleBold:       null,
            xaxisTitleSize:       null,
            xaxisTitleFont:       null,
            xaxisTitleColor:      null,
            xaxisTitleItalic:     null,
            xaxisTitleOffsetx:    0,
            xaxisTitleOffsety:    0,
            xaxisTitleX:          null,
            xaxisTitleY:          null,
            xaxisTitleHalign:     null,
            xaxisTitleValign:     null,
            
            labelsAbove:                  false,
            labelsAboveFont:              null,
            labelsAboveSize:              null,
            labelsAboveBold:              null,
            labelsAboveItalic:            null,
            labelsAboveColor:             null,
            labelsAboveBackground:        'rgba(255,255,255,0.5)',
            labelsAboveBackgroundPadding: 2,
            labelsAboveUnitsPre:          null,
            labelsAboveUnitsPost:         null,
            labelsAbovePoint:             null,
            labelsAboveThousand:          null,
            labelsAboveFormatter:         null,
            labelsAboveDecimals:          null,
            labelsAboveOffsetx:           0,
            labelsAboveOffsety:           0,
            labelsAboveHalign:            'center',
            labelsAboveValign:            'bottom',
            labelsAboveSpecific:          null,
            labelsAboveLastFont:              null,
            labelsAboveLastBold:              null,
            labelsAboveLastItalic:            null,
            labelsAboveLastSize:              null,
            labelsAboveLastColor:             null,
            labelsAboveLastBackground:        null,
            labelsAboveLastBackgroundPadding: null,

            textColor:            'black',
            textFont:             'Arial, Verdana, sans-serif',
            textSize:             12,
            textBold:             false,
            textItalic:           false,
            text:                 null,

            
            tooltips:                        null,
            tooltipsOverride:                null,
            tooltipsEffect:                  'fade',
            tooltipsCssClass:                'RGraph_tooltip',
            tooltipsCss:                     null,
            tooltipsEvent:                   'click',
            tooltipsPersistent:              false,
            tooltipsFormattedThousand:       ',',
            tooltipsFormattedPoint:          '.',
            tooltipsFormattedDecimals:       0,
            tooltipsFormattedUnitsPre:       '',
            tooltipsFormattedUnitsPost:      '',
            tooltipsFormattedKeyColors:      null,
            tooltipsFormattedKeyColorsShape: 'square',
            tooltipsFormattedKeyLabels:      [],
            tooltipsFormattedTableHeaders:   null,
            tooltipsFormattedTableData:      null,
            tooltipsPointer:                 true,
            tooltipsPointerOffsetx:          0,
            tooltipsPointerOffsety:          0,
            tooltipsPositionStatic:          true,

            highlightStroke:      'rgba(0,0,0,0)',
            highlightFill:        'rgba(255,255,255,0.7)',
            highlightLinewidth:   1,
            
            title:                '',
            titleX:               null,
            titleY:               null,
            titleHalign:          'center',
            titleValign:          null,
            titleSize:            null,
            titleColor:           null,
            titleFont:            null,
            titleBold:            null,
            titleItalic:          null,
            titleOffsetx:         0,
            titleOffsety:         0,

            titleSubtitle:        null,
            titleSubtitleSize:    null,
            titleSubtitleColor:   '#aaa',
            titleSubtitleFont:    null,
            titleSubtitleBold:    null,
            titleSubtitleItalic:  null,
            
            //shadow:               false,
            //shadowOffsetx:        2,
            //shadowOffsety:        2,
            //shadowBlur:           2,
            //shadowColor:        'rgba(0,0,0,0.25)',

            key:            null,
            keyColors:      null,
            keyOffsetx:     0,
            keyOffsety:     0,
            keyLabelsOffsetx: 0,
            keyLabelsOffsety: -1,
            keyLabelsFont:    null,
            keyLabelsSize:    null,
            keyLabelsColor:   null,
            keyLabelsBold:    null,
            keyLabelsItalic:  null,
            
            clip: null,
            
            zoom:             false
        };


        //
        // Add the reverse look-up table  for property names
        // so that property names can be specified in any case.
        //
        this.properties_lowercase_map = [];
        for (var i in this.properties) {
            if (typeof i === 'string') {
                this.properties_lowercase_map[i.toLowerCase()] = i;
            }
        }


        //
        // Copy the global object properties to this instance
        //
        RGraph.SVG.getGlobals(this);





        //
        // "Decorate" the object with the generic effects if the effects library has been included
        //
        if (RGraph.SVG.FX && typeof RGraph.SVG.FX.decorate === 'function') {
            RGraph.SVG.FX.decorate(this);
        }





        // Add the responsive function to the object
        this.responsive = RGraph.SVG.responsive;




        var properties = this.properties;








        //
        // The draw method draws the Bar chart
        //
        this.draw = function ()
        {
            // Fire the beforedraw event
            RGraph.SVG.fireCustomEvent(this, 'onbeforedraw');



            // Should be the first(ish) thing that's done in the
            // .draw() function except for the onbeforedraw event
            // and the installation of clipping.
            this.width  = Number(this.svg.getAttribute('width'));
            this.height = Number(this.svg.getAttribute('height'));








            //
            // If the xaxisLabels option is a string then turn it
            // into an array.
            //
            if (properties.xaxisLabels && properties.xaxisLabels.length) {
                if (typeof properties.xaxisLabels === 'string') {
                    properties.xaxisLabels = RGraph.SVG.arrayPad({
                        array:  [],
                        length: this.data.length + (properties.total ? 1 : 0),
                        value:  properties.xaxisLabels
                    });
                }

                //
                // Label substitution
                //
                for (var i=0; i<properties.xaxisLabels.length; ++i) {
                    properties.xaxisLabels[i] = RGraph.SVG.labelSubstitution({
                        object:    this,
                        text:      properties.xaxisLabels[i],
                        index:     i,
                        value:     this.data[i],
                        decimals:  properties.xaxisLabelsFormattedDecimals  || 0,
                        unitsPre:  properties.xaxisLabelsFormattedUnitsPre  || '',
                        unitsPost: properties.xaxisLabelsFormattedUnitsPost || '',
                        thousand:  properties.xaxisLabelsFormattedThousand  || ',',
                        point:     properties.xaxisLabelsFormattedPoint     || '.'
                    });
                }
            }








            // Create the defs tag if necessary
            RGraph.SVG.createDefs(this);



            this.coords      = []; // Reset this so it doesn't grow
            this.graphWidth  = this.width - properties.marginLeft - properties.marginRight;
            this.graphHeight = this.height - properties.marginTop - properties.marginBottom;



            // Parse the colors for gradients
            RGraph.SVG.resetColorsToOriginalValues({object:this});
            this.parseColors();
            
            
            
            
            // Work out the sum of the data and add it to the data
            if (properties.total && !this.totalAdded) {

                this.totalAdded = true;
            
                var sum = RGraph.SVG.arraySum(this.data);
                
                // Now append the sum to the data
                this.data.push(sum);
                
                // May need to append something to the labels array if properties.total
                // is enabled, so that the labels line up

                if (properties.xaxisLabels && properties.xaxisLabels.length === (this.data.length - 1)) {
                    properties.xaxisLabels.push('');
                }
            }




            for (var i=0,max=0,runningTotal=0; i<this.data.length - (properties.total ? 1 : 0); ++i) {
                runningTotal += this.data[i]
                max = Math.max(Math.abs(max), Math.abs(runningTotal));
            }

            // A custom, user-specified maximum value
            if (typeof properties.yaxisScaleMax === 'number') {
                max = properties.yaxisScaleMax;
            }
            
            // Set the ymin to zero if it's set mirror
            if (properties.yaxisScaleMin === 'mirror' || properties.yaxisScaleMin === 'middle' || properties.yaxisScaleMin === 'center') {
                var mirrorScale = true;
                properties.yaxisScaleMin   = 0;
            }


            //
            // Generate an appropiate scale
            //
            this.scale = RGraph.SVG.getScale({
                object:    this,
                numlabels: properties.yaxisLabelsCount,
                unitsPre:  properties.yaxisScaleUnitsPre,
                unitsPost: properties.yaxisScaleUnitsPost,
                max:       max,
                min:       properties.yaxisScaleMin,
                point:     properties.yaxisScalePoint,
                round:     properties.yaxisScaleRound,
                thousand:  properties.yaxisScaleThousand,
                decimals:  properties.yaxisScaleDecimals,
                strict:    typeof properties.yaxisScaleMax === 'number',
                formatter: properties.yaxisScaleFormatter
            });
                


            //
            // Get the scale a second time if the ymin should be mirored
            //
            // Set the ymin to zero if it's set mirror
            if (mirrorScale) {
                this.scale = RGraph.SVG.getScale({
                    object:    this,
                    numlabels: properties.yaxisLabelsCount,
                    unitsPre:  properties.yaxisScaleUnitsPre,
                    unitsPost: properties.yaxisScaleUnitsPost,
                    max:       this.scale.max,
                    min:       this.scale.max * -1,
                    point:     properties.yaxisScalePoint,
                    round:     false,
                    thousand:  properties.yaxisScaleThousand,
                    decimals:  properties.yaxisScaleDecimals,
                    strict:    true,
                    formatter: properties.yaxisScaleFormatter
                });
            }

            // Now the scale has been generated adopt its max value
            this.max      = this.scale.max;
            this.min      = this.scale.min;
            properties.yaxisScaleMax = this.scale.max;
            properties.yaxisScaleMin = this.scale.min;









            // Install clipping if requested
            if (this.properties.clip) {

                this.clipid = RGraph.SVG.installClipping(this);

                // Add the clip ID to the all group
                this.svgAllGroup.setAttribute(
                    'clip-path',
                    'url(#{1})'.format(this.clipid)
                );
            } else {
                // No clipping - so ensure that there's no clip-path
                // attribute
                this.clipid = null;
                this.svgAllGroup.removeAttribute('clip-path');
            }







            // Draw the background first
            RGraph.SVG.drawBackground(this);
            
            
            //
            // Create the group that the first bar will site in
            //
            this.create('<g id="first-bar-group"></g>', this.svgAllGroup);



            // Draw the axes BEFORE the bars
            RGraph.SVG.drawXAxis(this);
            RGraph.SVG.drawYAxis(this);


            // Draw the bars
            this.drawBars();
            
            
            // Draw the labelsAbove labels
            this.drawLabelsAbove();








            
            
            // Draw the key
            if (typeof properties.key !== null && RGraph.SVG.drawKey) {
                RGraph.SVG.drawKey(this);
            } else if (!RGraph.SVG.isNullish(properties.key)) {
                alert('The drawKey() function does not exist - have you forgotten to include the key library?');
            }











            // Add the event listener that clears the highlight rect if
            // there is any. Must be MOUSEDOWN (ie before the click event)
            //var obj = this;
            //document.body.addEventListener('mousedown', function (e)
            //{
            //    //RGraph.SVG.removeHighlight(obj);
            //
            //}, false);








            //
            // Allow the addition of custom text via the
            // text: property.
            //
            RGraph.SVG.addCustomText(this);





            // Lastly - install the zoom event listeners if
            // requested
            if (this.properties.zoom) {
                RGraph.SVG.addZoom(this);
            }












            //
            // Fire the onfirstdraw event
            //
            if (this.firstDraw) {
                this.firstDraw = false;
                RGraph.SVG.fireCustomEvent(this, 'onfirstdraw');
            }




            // Fire the draw event
            RGraph.SVG.fireCustomEvent(this, 'ondraw');







            //
            // Install any inline responsive configuration. This
            // should be last in the draw function - even after
            // the draw events.
            //
            RGraph.SVG.installInlineResponsive(this);












            return this;
        };








        //
        // New create() shortcut function
        // For example:
        //    this.create('rect,x:0,y:0,width:100,height:100'[,parent]);
        //
        // @param str string The tag definition to parse and create
        // @param     object The (optional) parent element
        // @return    object The new tag
        //
        this.create = function (str)
        {
            var def = RGraph.SVG.create.parseStr(this, str);
            def.svg = this.svg;
            
            // By default the parent is the SVG tag - but if
            // requested then change it to the tag that has
            // been given
            if (arguments[1]) {
                def.parent = arguments[1];
            }

            return RGraph.SVG.create(def);
        };








        //
        // Draws the bars
        //
        this.drawBars = function ()
        {
            this.graphWidth  = this.width  - properties.marginLeft - properties.marginRight;
            this.graphHeight = this.height - properties.marginTop - properties.marginBottom;
            
            // The width of the bars
            var innerWidth = (this.graphWidth / this.data.length) - (2 * properties.marginInner),
                outerWidth = (this.graphWidth / this.data.length);


            // The starting Y coordinate
            var y     = this.getYCoord(0),
                total = 0;



            // Loop thru the data drawing the bars
            for (var i=0; i<this.data.length; ++i) {
            
                var prevValue    = this.data[i - 1],
                    nextValue    = this.data[i + 1],
                    currentValue = this.data[i],
                    prevTotal    = total;

                total += parseFloat(this.data[i]) || 0;

                // Figure out the height
                var height = Math.abs((this.data[i] / (this.scale.max - this.scale.min) ) * this.graphHeight);










                // Work out the starting coord
                if (RGraph.SVG.isNullish(prevValue)) {
                    
                    if (currentValue > 0) {
                        y = this.getYCoord(prevTotal) - height;
                    } else {
                        y = this.getYCoord(prevTotal);
                    }

                } else {
                    if (i == 0 && this.data[i] > 0) {
                        y = y - height;
    
                    } else if (this.data[i] > 0 && this.data[i - 1] > 0) {
                        y = y - height;
    
                    } else if (this.data[i] > 0 && this.data[i - 1] < 0) {
                        y = y + prevHeight - height;
    
                    } else if (this.data[i] < 0 && this.data[i - 1] > 0) {
                        // Nada
    
                    } else if (this.data[i] < 0 && this.data[i - 1] < 0) {
                        y = y + prevHeight;
                    }
                }
                
                //
                // Determine the color
                //
                var fill = this.data[i] > 0 ? properties.colors[0] : properties.colors[1];
                
                if (properties.colorsSequential) {
                    fill = properties.colors[i];
                }

                
                
                
                
                // The last (the total) value if required
                if (properties.total) {
                    if (i === (this.data.length - 1) && this.data[this.data.length - 1] >= 0) {
                        
                        y = this.getYCoord(0) - height;
    
                        if (!properties.colorsSequential) {
                            fill = properties.colors[2];
                        }
                    } else if (i === (this.data.length - 1) && this.data[this.data.length - 1] < 0) {
                        y    = this.getYCoord(0);
    
                        if (!properties.colorsSequential) {
                            fill = properties.colors[2];
                        }
                    }
                }





                // Calculate the X coordinate
                var x = properties.marginLeft + (outerWidth * i) + properties.marginInner;





                // This handles an intermediate total
                if (this.data[i] === null || typeof this.data[i] === 'undefined') {

                    var axisY = this.getYCoord(0);
                    
                    if (prevValue < 0) {
                        y = prevY + prevHeight;
                    } else {
                        y = prevY;
                    }

                    height = this.getYCoord(0) - this.getYCoord(total);
                    
                    // Do this if not sequential colors
                    if (!properties.colorsSequential) {
                        fill   = properties.colors[3] || properties.colors[2];
                    }
                    
                    if (height < 0) {
                        y += height;
                        height *= -1;
                    }
                }



                //
                // Set a shadow if requested
                //
                if (properties.shadow) {
                    RGraph.SVG.setShadow({
                        object:  this,
                        offsetx: properties.shadowOffsetx,
                        offsety: properties.shadowOffsety,
                        blur:    properties.shadowBlur,
                        color:   properties.shadowColor,
                        id:      'dropShadow'
                    });
                }


                // Create the rect object
                var rect = RGraph.SVG.create({
                    svg: this.svg,
                    type: 'rect',

                    // Add the bar to the first-bar-group if
                    // drawing the first bar
                    parent: (i === 0 || (properties.total && i === (this.data.length - 1))) ? document.getElementById('first-bar-group') : this.svgAllGroup,

                    attr: {
                        x: x,
                        y: y,
                        width: innerWidth,
                        height: height,
                        stroke: properties.colorsStroke,
                        fill: fill,
                        'stroke-width': properties.linewidth,
                        'shape-rendering': 'crispEdges',
                        'data-index': i,
                        'data-original-x': x,
                        'data-original-y': y,
                        'data-original-width': innerWidth,
                        'data-original-height': height,
                        'data-original-stroke': properties.colorsStroke,
                        'data-original-fill': fill,
                        'data-value': String(this.data[i]),
                        filter: properties.shadow ? 'url(#dropShadow)' : '',
                    }
                });
                
                // Store the coordinates
                this.coords.push({
                    object:  this,
                    element: rect,
                    x:       x,
                    y:       y,
                    width:   innerWidth,
                    height:  height
                });








                // Add the tooltips
                if (!RGraph.SVG.isNullish(properties.tooltips) && (properties.tooltips[i] || typeof properties.tooltips === 'string') ) {

                    var obj = this;

                    //
                    // Add tooltip event listeners
                    //
                    (function (idx)
                    {
                        rect.addEventListener(properties.tooltipsEvent.replace(/^on/, ''), function (e)
                        {
                            obj.removeHighlight();

                            // Show the tooltip
                            RGraph.SVG.tooltip({
                                object:  obj,
                                 index:  idx,
                                 group:  0,
                       sequentialIndex:  idx,
                                  text:  typeof properties.tooltips === 'string' ? properties.tooltips : properties.tooltips[idx],
                                 event:  e
                            });
                            
                            // Highlight the rect that has been clicked on
                            obj.highlight(e.target);
                        }, false);

                        rect.addEventListener('mousemove', function (e)
                        {
                            e.target.style.cursor = 'pointer'
                        }, false);
                    })(i);
                }










                // Store these for the next iteration of the loop
                var prevX      = x,
                    prevY      = y,
                    prevWidth  = innerWidth,
                    prevHeight = height,
                    prevValue  = this.data[i];
            }




















            // Now draw the connecting lines
            for (var i=0; i<this.coords.length; ++i) {

                if (this.coords[i+1] && this.coords[i+1].element) {
                    
                    var x1 = Number(this.coords[i].element.getAttribute('x')) + Number(this.coords[i].element.getAttribute('width')),
                        y1 = parseInt(this.coords[i].element.getAttribute('y')) +          (this.data[i] > 0 ? 0 : parseInt(this.coords[i].element.getAttribute('height')) ),
                        x2 = x1 + (2 * properties.marginInner),
                        y2 = parseInt(this.coords[i].element.getAttribute('y')) +          (this.data[i] > 0 ? 0 : parseInt(this.coords[i].element.getAttribute('height')) );

                    // Handle total columns
                    if(this.coords[i].element.getAttribute('data-value') === 'null') {
                        if (i === (this.data.length - 1) ) {
                            y1 = parseFloat(this.coords[i].element.getAttribute('y'));
                            y2 = parseFloat(y1);
                        }

                        if (this.totalColumns[i]) {
                            // Calculate the total thus far
                            for (var k=0,total=0; k<i; ++k) {
                                total += this.data[k];
                            }
                        
                            if (total > 0 && this.data[i-1] > 0) {
                                y1 = Number(this.coords[i-1].element.getAttribute('y'));
                                y2 = y1;
                            } else if (total > 0 && this.data[i-1] < 0) {
                                y1 = Number(this.coords[i-1].element.getAttribute('y')) + Number(this.coords[i-1].element.getAttribute('height'));
                                y2 = y1;
                            }
                        }
                    }


                    var line = RGraph.SVG.create({
                        svg: this.svg,
                        type: 'line',
                        parent: this.svgAllGroup,
                        attr: {
                            x1: x1,
                            y1: y1,
                            x2: x2,
                            y2: y2,
                            stroke: properties.colorsConnectors || properties.colorsStroke,
                            'stroke-width': properties.linewidth,
                            'data-index': i,
                            'data-original-x1': x1,
                            'data-original-y1': y1,
                            'data-original-x2': x2,
                            'data-original-y2': y2
                        }
                    });
                    
                    // Store the connector details
                    this.coordsConnectors[i] = {
                        x1: x1,
                        y1: y1,
                        x2: x2,
                        y2: y2,
                        element: line
                    };
                }
            }
        };








        //
        // This function can be used to retrieve the relevant Y coordinate for a
        // particular value.
        // 
        // @param int value The value to get the Y coordinate for
        //
        this.getYCoord = function (value)
        {
            if (value > this.scale.max) {
                return null;
            }

            var y, xaxispos = properties.xaxispos;

            if (value < this.scale.min) {
                return null;
            }

            y  = ((value - this.scale.min) / (this.scale.max - this.scale.min));
            y *= (this.height - properties.marginTop - properties.marginBottom);

            y = this.height - properties.marginBottom - y;

            return y;
        };








        //
        // This function can be used to highlight a bar on the chart
        // 
        // @param object rect The rectangle to highlight
        //
        this.highlight = function (rect)
        {
            var x      = parseFloat(rect.getAttribute('x')) - 0.5,
                y      = parseFloat(rect.getAttribute('y')) - 0.5,
                width  = parseFloat(rect.getAttribute('width')) + 1,
                height = parseFloat(rect.getAttribute('height')) + 1;
            
            var highlight = RGraph.SVG.create({
                svg: this.svg,
                type: 'rect',
                parent: this.svgAllGroup,
                attr: {
                    stroke: properties.highlightStroke,
                    fill: properties.highlightFill,
                    x: x,
                    y: y,
                    width: width,
                    height: height,
                    'stroke-width': properties.highlightLinewidth
                },
                style: {
                    pointerEvents: 'none'
                }
            });


            // Store the highlight rect in the rebistry so
            // it can be cleared later
            RGraph.SVG.REG.set('highlight', highlight);
        };








        //
        // This allows for easy specification of gradients
        //
        this.parseColors = function () 
        {
            // Save the original colors so that they can be restored when
            // the canvas is cleared
            if (!Object.keys(this.originalColors).length) {
                this.originalColors = {
                    colors:              RGraph.SVG.arrayClone(properties.colors, true),
                    backgroundGridColor: RGraph.SVG.arrayClone(properties.backgroundGridColor, true),
                    highlightFill:       RGraph.SVG.arrayClone(properties.highlightFill, true),
                    backgroundColor:     RGraph.SVG.arrayClone(properties.backgroundColor, true)
                }
            }

            
            // colors
            var colors = properties.colors;

            if (colors) {
                for (var i=0; i<colors.length; ++i) {
                    colors[i] = RGraph.SVG.parseColorLinear({
                        object: this,
                        color: colors[i]
                    });
                }
            }

            properties.backgroundGridColor = RGraph.SVG.parseColorLinear({object: this, color: properties.backgroundGridColor});
            properties.highlightFill       = RGraph.SVG.parseColorLinear({object: this, color: properties.highlightFill});
            properties.backgroundColor     = RGraph.SVG.parseColorLinear({object: this, color: properties.backgroundColor});
        };








        //
        // Draws the labelsAbove
        //
        this.drawLabelsAbove = function ()
        {
            // Go through the above labels
            if (properties.labelsAbove) {
            
                var total = 0;

                for (var i=0; i<this.coords.length; ++i) {
                    
                    var num    = this.data[i],
                        total  = total + num;

                    if (typeof num === 'number' || RGraph.SVG.isNullish(num)) {
                        
                        if (RGraph.SVG.isNullish(num)) {
                            num = total;
                        }

                        var str = RGraph.SVG.numberFormat({
                            object:    this,
                            num:       num.toFixed(properties.labelsAboveDecimals),
                            prepend:   typeof properties.labelsAboveUnitsPre  === 'string'   ? properties.labelsAboveUnitsPre  : null,
                            append:    typeof properties.labelsAboveUnitsPost === 'string'   ? properties.labelsAboveUnitsPost : null,
                            point:     typeof properties.labelsAbovePoint     === 'string'   ? properties.labelsAbovePoint     : null,
                            thousand:  typeof properties.labelsAboveThousand  === 'string'   ? properties.labelsAboveThousand  : null,
                            formatter: typeof properties.labelsAboveFormatter === 'function' ? properties.labelsAboveFormatter : null
                        });

                        // Facilitate labelsAboveSpecific
                        if (properties.labelsAboveSpecific && properties.labelsAboveSpecific.length && (typeof properties.labelsAboveSpecific[i] === 'string' || typeof properties.labelsAboveSpecific[i] === 'number') ) {
                            str = properties.labelsAboveSpecific[i];
                        } else if ( properties.labelsAboveSpecific && properties.labelsAboveSpecific.length && typeof properties.labelsAboveSpecific[i] !== 'string' && typeof properties.labelsAboveSpecific[i] !== 'number') {
                            continue;
                        }
    
                        var x = parseFloat(this.coords[i].element.getAttribute('x')) + parseFloat(this.coords[i].element.getAttribute('width') / 2) + properties.labelsAboveOffsetx;
    
                        if (this.data[i] >= 0) {
                            var y = parseFloat(this.coords[i].element.getAttribute('y')) - 7 + properties.labelsAboveOffsety;
                            var valign = properties.labelsAboveValign;
                        } else {
                            var y = parseFloat(this.coords[i].element.getAttribute('y')) + parseFloat(this.coords[i].element.getAttribute('height')) + 7 - properties.labelsAboveOffsety;
                            var valign = properties.labelsAboveValign === 'top' ? 'bottom' : 'top';
                        }









                        // Formatting options for the labels
                        //
                        // NB The last label can have different formatting
                        if (properties.total && i === (this.coords.length - 1) ) {

                            var background = properties.labelsAboveLastBackground        || properties.labelsAboveBackground        || null;
                            var padding    = (typeof properties.labelsAboveLastBackgroundPadding === 'number' ? properties.labelsAboveLastBackgroundPadding : properties.labelsAboveBackgroundPadding) || 0;
                            var textConf   = {};

                            // font and color
                            textConf.font  =   properties.labelsAboveLastFont
                                            || properties.labelsAboveFont
                                            || properties.textFont;
                            
                            textConf.color =   properties.labelsAboveLastColor
                                            || properties.labelsAboveColor
                                            || properties.textColor;

                            // Size
                            if (typeof properties.labelsAboveLastSize === 'number') {
                                textConf.size = properties.labelsAboveLastSize;
                            } else if (typeof properties.labelsAboveSize === 'number') {
                                textConf.size = properties.labelsAboveSize;
                            } else {
                                textConf.size = properties.textSize;
                            }

                            // Bold
                            if (typeof properties.labelsAboveLastBold === 'boolean') {
                                textConf.bold = properties.labelsAboveLastBold;
                            } else if (typeof properties.labelsAboveBold === 'boolean') {
                                textConf.bold = properties.labelsAboveBold;
                            } else {
                                textConf.bold = properties.textBold;
                            }

                            // Italic
                            if (typeof properties.labelsAboveLastItalic === 'boolean') {
                                textConf.italic = properties.labelsAboveLastItalic;
                            } else if (typeof properties.labelsAboveItalic === 'boolean') {
                                textConf.italic = properties.labelsAboveItalic;
                            } else {
                                textConf.italic = properties.textItalic;
                            }

                        } else {

                            var background = properties.labelsAboveBackground        || null,
                                padding    = properties.labelsAboveBackgroundPadding || 0;

                            // Get the text configuration
                            var textConf = RGraph.SVG.getTextConf({
                                object: this,
                                prefix: 'labelsAbove'
                            });
                        }





                        RGraph.SVG.text({
                            object:     this,
                            parent:     this.svgAllGroup,
                            tag:        'labels.above',
                            
                            text:       str,
                            
                            x:          x,
                            y:          y,

                            halign:     properties.labelsAboveHalign,
                            valign:     valign,
                            
                            font:       textConf.font,
                            size:       textConf.size,
                            bold:       textConf.bold,
                            italic:     textConf.italic,
                            color:      textConf.color,
                            
                            background: background,
                            padding:    padding
                        });
                    }
                }
            }
        };








        //
        // Using a function to add events makes it easier to facilitate method
        // chaining
        // 
        // @param string   type The type of even to add
        // @param function func 
        //
        this.on = function (type, func)
        {
            if (type.substr(0,2) !== 'on') {
                type = 'on' + type;
            }
            
            RGraph.SVG.addCustomEventListener(this, type, func);
    
            return this;
        };








        //
        // Used in chaining. Runs a function there and then - not waiting for
        // the events to fire (eg the onbeforedraw event)
        // 
        // @param function func The function to execute
        //
        this.exec = function (func)
        {
            func(this);
            
            return this;
        };








        //
        // Remove highlight from the chart (tooltips)
        //
        this.removeHighlight = function ()
        {
            RGraph.SVG.removeHighlight();
        };








        //
        // A worker function that handles Bar chart specific tooltip substitutions
        //
        this.tooltipSubstitutions = function (opt)
        {
            return {
                  index: opt.index,
                dataset: 0,
        sequentialIndex: opt.index,
                  value: this.data[opt.index],
                 values: [this.data[opt.index]]
            };
        };








        //
        // A worker function that returns the correct color/label/value
        //
        // @param object specific The indexes that are applicable
        // @param number index    The appropriate index
        //
        this.tooltipsFormattedCustom = function (specific, index)
        {
            var color, label, value;

            //
            // Check for null values (ie subtotals) and calculate the subtotal if required
            //
        
            // Determine the correct color to use
            var colors = properties.colors;
        
            if (   properties.tooltipsFormattedKeyColors
                && properties.tooltipsFormattedKeyColors[0]
                && properties.tooltipsFormattedKeyColors[1]
                && properties.tooltipsFormattedKeyColors[2]) {
        
                colors = properties.tooltipsFormattedKeyColors;
            //} else {
            //    colors = properties.colors;
            }
            
            color  = colors[0];
        
            // Change the color for negative bars
            if (specific.value < 0) {
                color = colors[1];
            }
        
            // Change the color for the last bar
            if ( (specific.index + 1) === this.data.length || RGraph.SVG.isNullish(this.data[specific.index])) {
                color = colors[2];
            }




            // Figure out the correct label
            if (properties.tooltipsFormattedKeyLabels && typeof properties.tooltipsFormattedKeyLabels === 'object') {
            
                var isLast      = specific.index === (this.data.length - 1);
                var isNull      = RGraph.SVG.isNullish(this.data[specific.index]);
                var isPositive  = specific.value > 0;
                var isNegative  = specific.value < 0;

                if (isLast) {
                    label = typeof properties.tooltipsFormattedKeyLabels[2] === 'string' ? properties.tooltipsFormattedKeyLabels[2] : '';
                } else if (!isLast && isNull) {
                    label = typeof properties.tooltipsFormattedKeyLabels[3] === 'string' ? properties.tooltipsFormattedKeyLabels[3] : '';
                } else if (typeof properties.tooltipsFormattedKeyLabels[0] === 'string' && isPositive && !isLast) {
                    label = properties.tooltipsFormattedKeyLabels[0];
                } else if (typeof properties.tooltipsFormattedKeyLabels[1] === 'string' && isNegative) {
                    label = properties.tooltipsFormattedKeyLabels[1];
                } else if (typeof properties.tooltipsFormattedKeyLabels[2] === 'string' && isLast) {
                    label = properties.tooltipsFormattedKeyLabels[2];
                }
            }





            //
            // Calculate the subtotal for null values which are
            // within the dataset
            //
            if (RGraph.SVG.isNullish(this.data[specific.index])) {
                
                // Calculate the total thus far
                for (var i=0,value=0; i<=specific.index; ++i) {
                    value += this.data[i];
                }
            }

            return {
                label: label,
                color: color,
                value: value
            };
        };








        //
        // This allows for static tooltip positioning
        //
        this.positionTooltipStatic = function (args)
        {
            var obj      = args.object,
                e        = args.event,
                tooltip  = args.tooltip,
                index    = args.index,
                svgXY    = RGraph.SVG.getSVGXY(obj.svg),
                coords   = this.coords[args.index];

            // Position the tooltip in the X direction
            args.tooltip.style.left = (
                  svgXY[0]                       // The X coordinate of the SVG tag
                + coords.x                       // The X coordinate of the bar on the chart
                - (tooltip.offsetWidth / 2)      // Subtract half of the tooltip width
                + (coords.width / 2)             // Add half of the bar width
            ) + 'px';

            args.tooltip.style.top  = (
                  svgXY[1]                       // The Y coordinate of the SVG tag
                + coords.y                       // The Y coordinate of the bar on the chart
                - tooltip.offsetHeight           // The height of the tooltip
                - 10                             // An arbitrary amount
            ) + 'px';


            
            // If the top of the tooltip is off the top of the page
            // then move the tooltip down
            if(parseFloat(args.tooltip.style.top) < 0) {
                args.tooltip.style.top  = (
                      svgXY[1]                       // The Y coordinate of the SVG tag
                    + coords.y                       // The Y coordinate of the bar on the chart
                    + (coords.height / 2)            // Add half the height of the bar
                    - tooltip.offsetHeight           // The height of the tooltip
                    - 10                             // An arbitrary amount
                ) + 'px';
            }
        };








        //
        // The Bar chart grow effect
        //
        // @param  object opt Options to control the effect
        // @return object     The "this" object (so that you can
        //                    chain the object)
        this.grow = function ()
        {
            var opt             = arguments[0] || {},
                frames          = opt.frames || 30,
                frame           = 0,
                obj             = this,
                originalHeights = [],
                originalY       = [];

            this.draw();
            
            // Get the origin
            var xaxisY = this.getYCoord(0)
            
            // Make an array of the original heights and y values
            for (let i=0; i<obj.coords.length; ++i) {
                originalHeights.push(obj.coords[i].height);
                originalY.push(obj.coords[i].y);
            }

            //
            // Copy the data
            //
            var data = RGraph.SVG.arrayClone(this.data, true);

            var iterate = function ()
            {
                var multiplier = frame / frames;
                

                // Loop through the data and modify the values
                for (var i=0; i<data.length; ++i) {
                    
                    // Update the data
                    obj.data[i] = multiplier * data[i];
                    var y      = xaxisY - originalY[i];
                    var height = originalHeights[i];

                    // Update the y coordinate of the bar
                    obj.coords[i].element.setAttribute(
                        'y',
                        xaxisY - (y * multiplier)
                    );
                    

                    // Update the height
                    obj.coords[i].element.setAttribute(
                        'height',
                        multiplier * originalHeights[i]
                    );
                    
                    // if there's a corresponding connector update the Y coords of
                    // that too
                    if (obj.coordsConnectors[i]) {
                        if (obj.data[i] > 0) {
                            obj.coordsConnectors[i].element.setAttribute('y1', xaxisY - (y * multiplier) );
                            obj.coordsConnectors[i].element.setAttribute('y2', xaxisY - (y * multiplier) );
                        } else {
                            obj.coordsConnectors[i].element.setAttribute('y1', xaxisY - (y * multiplier) + (multiplier * height) );
                            obj.coordsConnectors[i].element.setAttribute('y2', xaxisY - (y * multiplier) + (multiplier * height) );
                        }
                    }
                }

                //
                // Restart the loop or end the amnimation
                //
                if (frame++ < frames) {
                    RGraph.SVG.FX.update(iterate);

                } else {

                    //RGraph.SVG.redraw();
                    obj.svgAllGroup.replaceChildren();
                    obj.draw();
                    
                    if (opt.callback) {
                        (opt.callback)(obj);
                    }
                }
            };

            iterate();

            return this;
        };








        //
        // This function handles clipping to scale values. Because
        // each chart handles scales differently, a worker function
        // is needed instead of it all being done centrally.
        //
        // @param object clipPath The <clipPath> node
        //
        this.clipToScaleWorker = function (clipPath)
        {
            // The Regular expression is actually done by the
            // calling RGraph.clipTo.start() function  in the core
            // library
            if (RegExp.$1 === 'min') from = this.min; else from = Number(RegExp.$1);
            if (RegExp.$2 === 'max') to   = this.max; else to   = Number(RegExp.$2);

            var width  = this.width,
                y1     = this.getYCoord(from),
                y2     = this.getYCoord(to),
                height = Math.abs(y2 - y1),
                x      = 0,
                y      = Math.min(y1, y2);


            // Increase the height if the maximum value is "max"
            if (RegExp.$2 === 'max') {
                y = 0;
                height += this.properties.marginTop;
            }
        
            // Increase the height if the minimum value is "min"
            if (RegExp.$1 === 'min') {
                height += this.properties.marginBottom;
            }


            RGraph.SVG.create({
                svg:    this.svg,
                type:   'rect',
                parent: clipPath,
                attr: {
                    x:      x,
                    y:      y,
                    width:  width,
                    height: height
                }
            });
            
            // Now set the clip-path attribute on the first
            // Line charts all-elements group
            this.svgAllGroup.setAttribute(
                'clip-path',
                'url(#' + clipPath.id + ')'
            );
        };







        //
        // Set the options that the user has provided
        //
        for (i in conf.options) {
            if (typeof i === 'string') {
                this.set(i, conf.options[i]);
            }
        }
    };
            
    return this;

// End module pattern
})(window, document);