Source: teechart.js

/**
 * @preserve TeeChart(tm) for JavaScript(tm)
 * @fileOverview TeeChart for JavaScript(tm)
 * v3.10 Jun 2024
 * Copyright(c) 2012-2024 by Steema Software SL. All Rights Reserved.
 * http://www.steema.com
 *
 * Licensed with commercial and non-commercial attributes,
 * specifically: http://www.steema.com/licensing/html5
 *
 * JavaScript is a trademark of Oracle Corporation.
 */

/**
 * @author <a href="mailto:david@steema.com">Steema Software</a>
 * @version 2.7
 */

/**
 * @namespace TeeChart namespace, contains all classes and methods.
 */
var Tee=Tee || {};

/*global exports, window, requestAnimFrame, Image, clearTimeout, HTMLTextAreaElement,
clearInterval, parseText, document, parseXML, parseJSON, navigator, setInterval,
HTMLInputElement, HTMLCanvasElement */

(function() {
  "use strict";
 
 if (typeof exports !== 'undefined') exports.Tee=Tee;
 
 if (typeof window !== 'undefined') {
   window.requestAnimFrame = (function( /*callback*/ ){
       return window.requestAnimationFrame ||
       window.webkitRequestAnimationFrame ||
       window.mozRequestAnimationFrame ||
       window.oRequestAnimationFrame ||
       window.msRequestAnimationFrame ||
       function(callback){
           window.setTimeout(callback, 1000 / 60, new Date().getTime());
       };
   })();
 }

// IE8 does not support defineProperty, so just in case:
var obDefP;
try {
Object.defineProperty({}, "x", {});
obDefP = Object.defineProperty;
} catch (e) {}

// For "IE < 9" :
// http://stackoverflow.com/questions/2790001/fixing-javascript-array-functions-in-internet-explorer-indexof-foreach-etc
// Add ECMA262-5 Array methods if not supported natively
//
if (!("indexOf" in Array.prototype)) {
Array.prototype.indexOf = function (find, i /*opt*/) {
  if (i === undefined) i = 0;
  if (i < 0) i += this.length;
  if (i < 0) i = 0;
  for (var n = this.length; i < n; i++)
	if (i in this && this[i] === find) return i;
  return -1;
};
}

/**
* @memberOf Tee
* @public
* @constructor
* @class Represents an X,Y point.
* @param {Number} x Horizontal point position.
* @param {Number} y Vertical point position.
* @property {Number} x The horizontal coordinate.
* @property {Number} y The vertical coordinate.
*/
Tee.Point = function (x, y) {
this.x = x;
this.y = y;
};

function Point(x, y) {
return new Tee.Point(x, y);
}

function pointInLine(p, p1, p2, tolerance) {
function distance() {
  var dx, dy;

  if (p2.x == p1.x && p2.y == p1.y) {
	dx = p.x - p1.x;
	dy = p.y - p1.y;
  } else {
	dx = p2.x - p1.x;
	dy = p2.y - p1.y;

	var result =
	  ((p.x - p1.x) * dx + (p.y - p1.y) * dy) / (dx * dx + dy * dy);

	if (result < 0) {
	  dx = p.x - p1.x;
	  dy = p.y - p1.y;
	} else if (result > 1) {
	  dx = p.x - p2.x;
	  dy = p.y - p2.y;
	} else {
	  dx = p.x - (p1.x + result * dx);
	  dy = p.y - (p1.y + result * dy);
	}
  }

  return Math.sqrt(dx * dx + dy * dy);
}

if ((p.x == p1.x && p.y == p1.y) || (p.x == p2.x && p.y == p2.y))
  return true;
else return Math.abs(distance()) <= (tolerance + 1);
}

/**
* @memberOf Tee
* @public
* @constructor
* @class Represents a rectangle with origin xy position, width and height
* @param {Number} x The position of left side of rectangle.
* @param {Number} y The position of top side of rectangle.
* @param {Number} width Amount of rectangle width.
* @param {Number} height Amount of rectangle height.
* @property {Number} x The position of left side of rectangle.
* @property {Number} y The position of top side of rectangle.
* @property {Number} width Amount of rectangle width.
* @property {Number} height Amount of rectangle height.
*/
Tee.Rectangle = function (x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;

/**
 * Sets Rectangle properties.
 * @memberOf Tee.Rectangle
 * @param {Number} x The position of left side of rectangle.
 * @param {Number} y The position of top side of rectangle.
 * @param {Number} width Amount of rectangle width.
 * @param {Number} height Amount of rectangle height.
 */
this.set = function (x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
};

/**
 * Sets Rectangle properties from rectangle r parameter.
 * @public
 * @memberOf Tee.Rectangle
 * @param {Tee.Rectangle} r The Rectangle instance to copy values from.
 */
this.setFrom = function (r) {
  this.x = r.x;
  this.y = r.y;
  this.width = r.width;
  this.height = r.height;
};

/**
 * @returns {Number} Returns the position in pixels of the right side of the
 * rectangle.
 */
this.getRight = function () {
  return this.x + this.width;
};

/**
 * @returns {Number} Returns the position in pixels of the bottom side of the
 * rectangle.
 */
this.getBottom = function () {
  return this.y + this.height;
};

/**
 * @param {Number} value Defines the position of top side of rectangle.
 */
this.setTop = function (value) {
  this.height -= value - this.y;
  this.y = value;
};

/**
 * @param {Number} value Defines the position of bottom side of rectangle.
 */
this.setBottom = function (value) {
  this.height = value - this.y;
};

/**
 * @param {Number} value Defines the position of left side of rectangle.
 */
this.setLeft = function (value) {
  this.width -= value - this.x;
  this.x = value;
};

/**
 * @param {Number} value Defines the position of right side of rectangle.
 */
this.setRight = function (value) {
  this.width = value - this.x;
};

/**
 * @returns {Boolean} Returns if {@link Tee.Point} p is inside the rectangle.
 * @param {Tee.Point} p XY position to test.
 */
this.contains = function (p) {
  return (
	p.x >= this.x &&
	p.x <= this.x + this.width &&
	p.y >= this.y &&
	p.y <= this.y + this.height
  );
};

/**
 *
 * @param {Number} x Horizontal pixels.
 * @param {Number} y Vertical pixels.
 */
this.offset = function (x, y) {
  this.x += x;
  this.y += y;
};
};

  function Rectangle(x, y, width, height) {
    return new Tee.Rectangle(x, y, width, height);
  }

  /**
   * Sets Rectangle properties.
   * @memberOf Tee.Rectangle
   * @param {Number} x The position of left side of rectangle.
   * @param {Number} y The position of top side of rectangle.
   * @param {Number} width Amount of rectangle width.
   * @param {Number} height Amount of rectangle height.
   */
  /*Rectangle.prototype.set=function(x,y,width,height) {
  this.x=x;
  this.y=y;
  this.width=width;
  this.height=height;
  };*/

    /**
     * Sets Rectangle properties from rectangle r parameter.
     * @public
     * @memberOf Tee.Rectangle
     * @param {Tee.Rectangle} r The Rectangle instance to copy values from.
     */
    /*Rectangle.prototype.setFrom=function(r) {
    this.x=r.x;
    this.y=r.y;
    this.width=r.width;
    this.height=r.height;
  };*/

    /**
     * @returns {Number} Returns the position in pixels of the right side of the
     * rectangle.
     */
    //Rectangle.prototype.getRight=function() { return this.x+this.width; };

    /**
     * @returns {Number} Returns the position in pixels of the bottom side of the
     * rectangle.
     */
    //Rectangle.prototype.getBottom=function() { return this.y+this.height; };

    /**
     * @param {Number} value Defines the position of top side of rectangle.
     */
    /*Rectangle.prototype.setTop=function(value) {
    this.height -= (value-this.y);
    this.y=value;
  };*/

    /**
     * @param {Number} value Defines the position of bottom side of rectangle.
     */
    /*Rectangle.prototype.setBottom=function(value) {
    this.height = value - this.y;
  };*/

    /**
     * @param {Number} value Defines the position of left side of rectangle.
     */
    /*Rectangle.prototype.setLeft=function(value) {
    this.width -= (value-this.x);
    this.x=value;
  };*/

    /**
     * @param {Number} value Defines the position of right side of rectangle.
     */
    /*Rectangle.prototype.setRight=function(value) {
    this.width = value - this.x;
  };*/

    /**
     * @returns {Boolean} Returns if {@link Tee.Point} p is inside the rectangle.
     * @param {Tee.Point} p XY position to test.
     */
    /*Rectangle.prototype.contains=function(p) {
    return (p.x>=this.x) && (p.x<=(this.x+this.width)) &&
          (p.y>=this.y) && (p.y<=(this.y+this.height));
  };*/

    /**
     *
     * @param {Number} x Horizontal pixels.
     * @param {Number} y Vertical pixels.
     */
    /*Rectangle.prototype.offset=function(x,y) {
    this.x+=x;
    this.y+=y;
  };*/

  /**
   * @memberOf Tee
   * @constructor
   * @class Values for each side (left, top, right and bottom) as percentage margins.
   * @property {Number} [left=2] Amount of left margin as percent of chart width.
   * @property {Number} [top=2] Amount of top margin as percent of chart height.
   * @property {Number} [right=2] Amount of right margin as percent of chart width.
   * @property {Number} [bottom=2] Amount of bottom margin as percent of chart height.
   */
  function Margins() {
    this.left = this.right = this.top = this.bottom = 2;

    /*
     * @private
     */
    this.apply = function (r) {
      var w = r.width,
        h = r.height;

      r.x += w * this.left * 0.01;
      r.width -= w * Math.min(100, this.left + this.right) * 0.01;

      r.y += h * this.top * 0.01;
      r.height -= h * Math.min(100, this.top + this.bottom) * 0.01;
    };
  }

  /**
   * @constructor
   * @class Abstract base class to represent a "tool"
   * @param {Tee.Chart} chart The parent chart this tool belongs to.
   * @property {Boolean} [active=true] Determines if this tool will be painted or enabled.
   */
  Tee.Tool = function (chart) {
    this.chart = chart;
    this.active = true;
  };

  /**
   * @constructor
   * @augments Tee.Tool
   * @class Base abstract class to perform Animations.
   * @property {Number} [duration=500] Duration in milliseconds of the animation.
   * @property {boolean} [loop=false] When true, the animation never stops (starts again when finished).
   * @property {boolean} [running=false] Read-only, returns if the animation is currently running.
   * @property {Tee.Animation[]} items Sub-animations that are executed in parallel with self.
   * @property {boolean} [autoDraw=true] When true, the animation repaints the chart at every step.
   */
  Tee.Animation = function (target, onstep) {
    Tee.Tool.call(this, target);

    this.active = true;
    this.mode = "linear";
    this.duration = 500;
    this.items = [];

    this.autoDraw = true;
    this.loop = false;
    this.running = false;

    this.onstart = null;
    this.onstop = null;

    if (target)
      if (target instanceof Tee.Chart) this.chart = target;
      else if (target instanceof Tee.Animation) {
        this.chart = target.chart;
        target.items.push(this);
      }

    var o = null;

    this._dostart = function () {
      this.init = new Date().getTime();

      o.start();
      for (var t = 0, i; (i = o.items[t++]); )
        if (i.active) {
          i.chart = o.chart;
          i.start();
        }

      o.chart.draw();
      requestAnimFrame(this.step, this);
    };

    this.animate = function (chart) {
      if (!this.running) {
        this.running = true;

        if (chart) this.chart = chart;

        o = this;
        this._dostart();
      }
    };

    this.start = function () {
      if (this.onstart) this.onstart();
    };
    this.stop = function () {
      if (this.onstop) this.onstop();
    };

    this.doStep = function (f) {
      if (onstep) onstep(f);
    };

    this.step = function () {
      var now = new Date().getTime(),
        t,
        i,
        tmp = (now - o.init) / o.duration,
        f = o.mode == "linear" ? tmp : Math.pow(2, 10 * (tmp - 1));

      if (f >= 0 && f < 1) {
        if (o.running) {
          o.doStep(f);

          for (t = 0; (i = o.items[t++]); )
            if (i.active) {
              i.chart = o.chart;
              i.doStep(f);
            }

          if (o.autoDraw) o.chart.draw();

          requestAnimFrame(o.step, o);
        }
      } else {
        o.stop();
        for (t = 0; (i = o.items[t++]); )
          if (i.active) {
            i.chart = o.chart;
            i.stop();
          }

        if (o.onstop) o.onstop(o);

        if (o.loop) o._dostart();
        else {
          o.running = false;
          o.chart.draw();
        }
      }
    };
  };

  Tee.Animation.prototype = new Tee.Tool();

  /**
   * @constructor
   * @class Draws a glow animation behind the bounds rectangle property
   * @param {Number} duration Animation duration in milliseconds.
   * @param {Tee.Rectangle} bounds The rectangle to apply the animation.
   * @param {Tee.Format} format The formatting properties to paint the bounds rectangle.
   */
  function AnimateHover(duration, bounds, format) {
    this.format = format;
    this.bounds = bounds;

    var o = this,
      s = format.shadow;

    this.old = new Shadow();
    this.old.set(s);

    s.visible = true;
    s.color = "rgba(0,255,0,0.1)";
    s.blur = 10;
    s.width = 0;
    s.height = 0;

    this.enabled = true;

    var a = new Tee.Animation(format.chart, function (f) {
      if (!o.enabled) return;

      if (f < 1) o.format.shadow.color = "rgba(0,255,0," + f.toString() + ")";
      else if (o.autoHide) o.restore();
    });

    a.duration = duration;
    a.animate();

    this.restore = function () {
      this.format.shadow.set(this.old);
      this.enabled = false;
    };
  }

  Tee.Tool.prototype.mousedown = function () {};
  Tee.Tool.prototype.mousemove = function () {};
  Tee.Tool.prototype.mouseout = function () {};
  Tee.Tool.prototype.clicked = function () {
    return false;
  };
  Tee.Tool.prototype.draw = function () {};

  /**
   * @constructor
   * @memberOf Tee
   * @class Colors and direction to fill areas with gradients
   * @param {Tee.Chart} chart The parent chart this gradient object belongs to.
   * @property {Boolean} [visible=false] Determines if contents will be filled using this gradient.
   * @property {Color[]} colors Array of colors to define the gradient.
   * @property {String} [direction="topbottom"] Defines the gradient orientation
   * ("topbottom", "bottomtop", "leftright", "rightleft", "radial", "diagonalup", "diagonaldown").
   * @property {Number[]} stops Array of percentages from 0 to 1, for each color in colors array.
   * @property {Point} offset For radial gradients, moves the center xy position.
   */
  function Gradient(chart) {
    this.chart = chart;
    this.visible = false;

    this.colors = ["white", "silver"];
    this.direction = "topbottom";
    this.stops = null;
    this.offset = { x: 0, y: 0 };

    /**
     * @returns {CanvasGradient} Returns a canvas gradient
     */
    this.create = function (r, color) {
      return this.rect(r.x, r.y, r.width, r.height, color);
    };

    /**
     * @returns {CanvasGradient} Returns a canvas gradient
     */
    this.rect = function (x, y, width, height, color) {
      var g,
        c = this.chart.ctx,
        l = c.createLinearGradient;

      if (this.direction == "topbottom") g = l.call(c, x, y, x, y + height);
      else if (this.direction == "bottomtop")
        g = l.call(c, x, y + height, x, y);
      else if (this.direction == "leftright") g = l.call(c, x, y, x + width, y);
      else if (this.direction == "rightleft") g = l.call(c, x + width, y, x, y);
      else if (this.direction == "radial") {
        var px = x + width * 0.5 + this.offset.x,
          py = y + height * 0.5 + this.offset.y,
          rad = Math.max(width, height);

        g = c.createRadialGradient(px, py, 0, px, py, rad);
      } else if (this.direction == "diagonalup")
        g = l.call(c, x, y + height, x + width, y);
      else g = l.call(c, x, y, x + width, y + height);

      if (color) this.setEndColor(color);
      //this.colors[0]=color;  // pie inverted effect

      var t,
        co = this.colors,
        len = co.length,
        s = this.stops,
        sl = s ? s.length : 0;

      if (len > 1)
        for (t = 0; t < len; t++)
          g.addColorStop(sl <= t ? t / (len - 1) : s[t], co[t]);
      else g.addColorStop(0, len > 0 ? co[0] : "white");

      return g;
    };
  }

  /**
   * @memberOf Tee.Gradient
   * Sets color to all gradient colors except first color.
   * @param {Color} color The color to set.
   */
  Gradient.prototype.setEndColor = function (color) {
    if (color && color !== "")
      for (var t = 1, l = this.colors.length; t < l; t++)
        this.colors[t] = color;
  };

  /**
   * @constructor
   * @memberOf Tee
   * @class Color and parameters to draw shadows behind areas
   * @param {Tee.Chart} chart The parent chart this shadow object belongs to.
   * @property {Boolean} [visible=false] Determines if contents will be filled with a backdrop shadow or not.
   * @property {Number} [blur=4] Amount of softness effect.
   * @property {Color} [color="DimGray"] The color used to draw the shadow.
   * @property {Number} [width=4] Amount in pixels to translate the shadow in horizontal direction.
   * @property {Number} [height=4] Amount in pixels to translate the shadow in vertical direction.
   */
  function Shadow(chart) {
    this.chart = chart;
    this.visible = false;
    this.blur = 4;
    this.color = "rgba(80,80,80,0.75)";
    this.width = 4;
    this.height = 4;

    this.prepare = function (c) {
      if (this.visible) {
        c.shadowBlur = this.blur;
        c.shadowColor = this.color;
        c.shadowOffsetX = this.width;
        c.shadowOffsetY = this.chart.isAndroid ? -this.height : this.height;
      } else c.shadowColor = "transparent";
    };
  }

  Shadow.prototype.set = function (s) {
    this.visible = s.visible;
    this.color = s.color;
    this.blur = s.blur;
    this.width = s.width;
    this.height = s.height;
  };

  /**
   * @constructor
   * @memberOf Tee
   * @class Image and url to draw images
   * @param {Tee.Chart} chart The parent chart this image object belongs to.
   * @property {Boolean} visible When true, the image is displayed.
   * @property {URL} [url=""] The source url to retrieve the image.
   * @property {HTMLImage} image The <a href="http://www.w3.org/2003/01/dom2-javadoc/org/w3c/dom/html2/HTMLImageElement.html">
   * html DOM Image component</a> to store or retrieve the image.
   */
  function ChartImage(chart) {
    this.url = "";
    this.repeat = "no-repeat";
    this.backFill = false;
    this.chart = chart;
    this.visible = true;

    this.tryDraw = function (x, y, width, height) {
      if (!this.image) {
        this.image = new Image();

        this.image.onload = function () {
          chart.draw();
        };
      }

      if (this.image.src === "") {
        chart = this.chart;
        this.image.src = this.url;
      } else if (chart.ctx.drawImage) {
        // Threejs check
        if (this.repeat == "repeat") {
          var pattern = chart.ctx.createPattern(this.image, "repeat");
          chart.ctx.fillStyle = pattern;
          chart.ctx.fillRect(x, y, width, height);
        } else {
          chart.ctx.drawImage(this.image, x, y, width, height);
        }
      }
    };
  }

  /**
   * @constructor
   * @memberOf Tee
   * @class Color and properties to draw lines
   * @param {Tee.Chart} chart The parent chart this stroke object belongs to.
   * @property {Color} fill Defines the color used to fill the stroke lines.
   * @property {Number} [size=1] Defines the size in pixels of the stroke lines.
   * @property {String} [join="round"] Controls how to paint unions between lines. (miter, round, bevel)
   * @property {String} [cap="square"] Controls how to paint ending line points. (square, round, butt)
   */
  function Stroke(chart) {
    this.chart = chart;
    this.fill = "black";
    this.size = 1;
    this.join = "round";
    this.cap = "square";
    this.dash = null;

    this._g = null;

    if (obDefP)
      obDefP(this, "gradient", {
        get: function () {
          if (!this._g) this._g = new Gradient(this.chart);
          return this._g;
        },
      });
    else this._g = this.gradient = new Gradient(chart);

    this.prepare = function (fill, c) {
      c = c || this.chart.ctx;
      var g = this._g;

      c.strokeStyle =
        g && g.visible ? g.create(this.chart.bounds) : fill ? fill : this.fill;

      c.lineWidth = this.size;
      c.lineJoin = this.join;
      c.lineCap = this.cap;

      c.shadowColor = "transparent";

      if (c.setLineDash) c.setLineDash(this.dash || []);
      // [] --> Safari WebKit exception
      else if (c.mozCurrentTransform) c.mozDash = this.dash;
      else if (this.chart.isChrome) c.webkitLineDash = this.dash;
    };

    /*
     * @private
     */
    this.setChart = function (chart) {
      this.chart = chart;
      if (this._g) this._g.chart = chart;
    };
  }

  /**
   * @memberOf Tee
   * @constructor
   * @class Style and fill properties to display text
   * @param {Tee.Chart} chart The parent chart this font object belongs to.
   * @property {String} [style="11px Tahoma"] Font family, size and attributes.
   * @property {Color} [fill="black"] Color used to fill texts using this font.
   * @property {Tee.Shadow} shadow Attributes to fill a shadow behind text.
   * @property {Tee.Stroke} stroke Attributes to draw an outline around text.
   * @property {String} [textAlign="center"] Defines to draw text at left, right or center inside container.
   */
  function Font(chart) {
    this.chart = chart;

    this.style = "11px Tahoma";

    this._g = null;

    if (obDefP)
      obDefP(this, "gradient", {
        get: function () {
          if (!this._g) this._g = new Gradient(this.chart);
          return this._g;
        },
      });
    else this._g = this.gradient = new Gradient(chart);

    this.fill = "black";

    this._sh = null;

    if (obDefP)
      obDefP(this, "shadow", {
        get: function () {
          if (!this._sh) this._sh = new Shadow(this.chart);
          return this._sh;
        },
      });
    else this._sh = this.shadow = new Shadow(chart);

    this._s = null;

    if (obDefP)
      obDefP(this, "stroke", {
        get: function () {
          if (!this._s) {
            this._s = new Stroke(this.chart);
            this._s.fill = "";
          }
          return this._s;
        },
      });
    else {
      this._s = this.stroke = new Stroke(chart);
      this._s.fill = "";
    }

    this.textAlign = "center";
    this.baseLine = "alphabetic";
  }

  /**
   * @returns {Number} Returns the size of font, or 20 if it can't be guessed.
   */
  Font.prototype.getSize = function () {
    var s = this.style.split(" "),
      t,
      res;
    for (t = 0; t < s.length; t++) {
      res = parseFloat(s[t]);
      if (res) return res;
    }

    return 20;
  };

  Font.prototype.setSize = function (value) {
    var tmp = "",
      s = this.style.split(" "),
      t;
    for (t = 0; t < s.length; t++)
      parseFloat(s[t])
        ? (tmp += value.toString() + "px ")
        : (tmp += s[t] + " ");

    this.style = tmp;
  };

  Font.prototype.prepare = function () {
    var c = this.chart.ctx;
    c.textAlign = this.textAlign;
    c.textBaseline = this.baseLine;

    if (this._sh) this._sh.prepare(c);

    if (c.font != this.style)
      // speed opt.
      c.font = this.style;
  };

  /**
   * @private
   **/
  Font.prototype.setChart = function (chart) {
    this.chart = chart;
    if (this._g) this._g.chart = chart;
    if (this._sh) this._sh.chart = chart;
    if (this._s) this._s.setChart(chart);
  };

  /**
   * Draws a dashed line.
   * @param {Number} x Starting line horizontal position in pixels.
   * @param {Number} y Starting line vertical position in pixels.
   * @param {Number} x2 Ending line horizontal position in pixels.
   * @param {Number} y2 Ending line vertical position in pixels.
   * @param {Number[]} [da] Optional array of dash offsets.
   */
  function dashedLine(ctx, x, y, x2, y2, da) {
    if (!da) da = [10, 5];

    ctx.save();

    var dx = x2 - x,
      dy = y2 - y,
      len = Math.sqrt(dx * dx + dy * dy),
      rot = Math.atan2(dy, dx);
    ctx.translate(x, y);
    ctx.moveTo(0, 0);
    ctx.rotate(rot);
    var dc = da.length,
      di = 0,
      draw = true;
    x = 0;
    while (len > x) {
      x += da[di++ % dc];
      if (x > len) x = len;
      draw ? ctx.lineTo(x, 0) : ctx.moveTo(x, 0);
      draw = !draw;
    }
    ctx.restore();
  }

  /**
   * @constructor
   * @public
   * @class Contains visual parameters like fill, shadow, image, font.
   * @param {Tee.Chart} chart The parent chart this format object belongs to.
   * @property {Tee.Gradient} gradient Gradient properties to fill contents.
   * @property {Color} fill Color used to paint contents interior.
   * @property {Tee.Stroke} stroke Properties to draw lines around boundaries.
   * @property {Tee.Shadow} shadow Properties to draw a backdrop shadow.
   * @property {Tee.Font} font Properties to fill text.
   * @property {Boolean} doSuperNums Default false. When true renders font as superscript.
   * @property {Boolean} doSubNums Default false. When true renders font as subscript.
   * @property {Tee.ChartImage} image Image to fill background.
   * @property {Tee.Point} round Width and height of rectangle rounded corners.
   * @property {Number} transparency Controls the transparency, from 0 (opaque) to 1 (transparent).
   */
  Tee.Format = function (chart) {
    this.chart = chart;

    this.gradient = new Gradient(chart);
    this.fill = "rgb(200,200,200)";

    this.stroke = new Stroke(chart);

    this.round = { x: 0, y: 0 };
    this.transparency = 0;

    this.doSuperNums = false;
    this.doSubNums = false;

    this.font = new Font(chart);

    this._img = null;

    if (obDefP)
      obDefP(this, "image", {
        get: function () {
          if (!this._img) this._img = new ChartImage(this.chart);
          return this._img;
        },
      });
    else this._img = this.image = new ChartImage(chart);

    this.shadow = new Shadow(chart);

    /**
     * Draws a rectangle with rounded corners
     * @param {Number} x Position in pixels of left side of rectangle.
     * @param {Number} y Position in pixels of top side of rectangle.
     * @param {Number} width Amount in pixels of rectangle width.
     * @param {Number} height Amount in pixels of rectangle height.
     * @param {Number} xr Amount in pixels of corners radius width.
     * @param {Number} yr Amount in pixels of corners radius height.
     * @param {Boolean[]} [corners] Optional, defines to paint top-left, top-right, bottom-left and bottom-right corners.
     */
    this.roundRect = function (ctx, x, y, width, height) {
      if (ctx.roundRect) {
        ctx.roundRect(x, y, width, height, this.round.x, this.round.y);
        return;
      }

      var r = x + width,
        b = y + height,
        xr = this.round.x,
        yr = this.round.y,
        c = this.round.corners;

      if (height < 0) {
        y = b;
        b = y - height;
      }

      if (width < 0) {
        x = r;
        r = x - width;
      }

      if (2 * xr > width) xr = width * 0.5;
      if (2 * yr > height) yr = height * 0.5;

      !c || c[0] ? ctx.moveTo(x + xr, y) : ctx.moveTo(x, y);

      if (!c || c[1]) {
        ctx.lineTo(r - xr, y);
        ctx.quadraticCurveTo(r, y, r, y + yr);
      } else ctx.lineTo(r, y);

      if (!c || c[2]) {
        ctx.lineTo(r, b - yr);
        ctx.quadraticCurveTo(r, b, r - xr, b);
      } else ctx.lineTo(r, b);

      if (!c || c[3]) {
        ctx.lineTo(x + xr, b);
        ctx.quadraticCurveTo(x, b, x, b - yr);
      } else ctx.lineTo(x, b);

      if (!c || c[0]) {
        ctx.lineTo(x, y + yr);
        ctx.quadraticCurveTo(x, y, x + xr, y);
      } else ctx.lineTo(x, y);

      ctx.closePath();
    };

    /**
     * @returns {Number} Returns the height in pixels of a given text using current font size and attributes.
     */
    this.textHeight = function (/*text*/) {
      return this.font.getSize() * 1.3;

      //var s=document.createElement("span");
      //s.font=this.font.style;
      //s.textContent=text;
      //return s.offsetHeight;

      //return 20;
    };

    /**
     * @returns {Number} Returns the width in pixels of a given text using current font size and attributes.
     */
    this.textWidth = function (text) {
      return this.chart.ctx.measureText(text).width;
    };

    this.fillBack = function (c, getbounds, x, y, width, height) {
      if (this.gradient.visible) {
        c.fillStyle = getbounds
          ? this.gradient.create(getbounds())
          : this.gradient.rect(x, y, width, height);
        c.fill();
      } else if (this.fill !== "") {
        c.fillStyle = this.fill;
        c.fill();
      }
    };

    // (Firefox bottleneck)

    this.draw = function (c, getbounds, x, y, width, height) {
      var i = this._img,
        oldtransp;

      if (typeof x === "object") {
        y = x.y;
        width = x.width;
        height = x.height;
        x = x.x;
      }

      if (this.transparency > 0) {
        oldtransp = c.globalAlpha;
        c.globalAlpha = (1 - this.transparency) * oldtransp;
      }

      this.shadow.prepare(c);

      if (i && i.visible && i.url !== "") {
        c.save();
        c.clip();

        if (i.backFill == true) {
          this.fillBack(c, getbounds, x, y, width, height);
        }

        if (getbounds) {
          var r = getbounds();
          i.tryDraw(r.x, r.y, r.width, r.height);
        } else i.tryDraw(x, y, width, height);

        c.restore();
      } else {
        this.fillBack(c, getbounds, x, y, width, height);
      }

      if (this.stroke.fill !== "") {
        this.stroke.prepare();
        c.stroke();
      }

      if (this.transparency > 0) c.globalAlpha = oldtransp;
    };

    this.subNums = function (str) {
      var newStr = "";

      for (var i = 0; i < str.length; i++) {
        //  Get the code of the current character
        var code = str.charCodeAt(i);
        if (code >= 48 && code <= 57) {
          //  If it's between "0" and "9", offset the code ...
          newStr += String.fromCharCode(code + 8272);
        } else {
          //   ... otherwise keep the character
          newStr += str[i];
        }
      }

      return newStr;
    };

    this.superNums = function (str) {
      var newStr = "";

      for (var i = 0; i < str.length; i++) {
        //  Get the code of the current character
        var code = str.charCodeAt(i);
        if (code == 48 || (code >= 52 && code <= 57)) {
          //  If it's between "0" and "9", offset the code ...
          newStr += String.fromCharCode(code + 8256);
        } else if (code == 49) newStr += String.fromCharCode(185);
        else if (code == 50) newStr += String.fromCharCode(178);
        else if (code == 51) newStr += String.fromCharCode(179);
        else {
          //   ... otherwise keep the character
          newStr += str[i];
        }
      }

      return newStr;
    };

    this.drawText = function (bounds, text) {
      var g = this.font._g,
        c = this.chart.ctx,
        s = this.font._s,
        a = this.font.textAlign,
        x = bounds.x,
        y = bounds.y;

      function xy(text) {
        c.fillText(text, x, y);

        if (s && s.fill !== "") {
          s.prepare();
          c.strokeText(text, x, y);
        }
      }

      c.fillStyle =
        g && g.visible && bounds ? g.create(bounds) : this.font.fill;

      if (a == "center") x += 0.5 * bounds.width;
      else if (a == "right" || a == "end") x += bounds.width;

      var rows = (text + "").split("\n"),
        l = rows.length;

      if (l > 1) {
        var h = this.textHeight(rows[0]);

        for (var t = 0; t < l; t++) {
          if (this.doSuperNums) xy(this.superNums(rows[t]));
          else if (this.doSubNums) xy(this.subNums(rows[t]));
          else xy(rows[t]);

          y += h;
        }
      } else {
        if (this.doSuperNums) xy(this.superNums(text));
        else if (this.doSubNums) xy(this.subNums(text));
        else xy(text);
      }
    };

    this.rectangle = function (x, y, width, height) {
      if (this.transparency < 1) {
        if (typeof x === "object") this.rectangle(x.x, x.y, x.width, x.height);
        else {
          this.rectPath(x, y, width, height);
          this.draw(this.chart.ctx, null, x, y, width, height);
        }
      }
    };

    // Returns "r" rectangle around points xy array.

    this.polygonBounds = function (points, r) {
      var x0 = 0,
        y0 = 0,
        x1 = 0,
        y1 = 0,
        l = points.length,
        p,
        t;

      if (l > 0) {
        x0 = x1 = points[0].x;
        y0 = y1 = points[0].y;

        for (t = 1; t < l; t++) {
          p = points[t].x;
          if (p < x0) x0 = p;
          else if (p > x1) x1 = p;
          p = points[t].y;
          if (p < y0) y0 = p;
          else if (p > y1) y1 = p;
        }
      }

      r.x = x0;
      r.y = y0;
      r.width = x1 - x0;
      r.height = y1 - y0;
    };

    var tmp = new Rectangle();

    this.polygon = function (points) {
      var c = this.chart.ctx,
        l = points.length,
        t;

      c.beginPath();
      c.moveTo(points[0].x, points[0].y);

      for (t = 1; t < l; t++) c.lineTo(points[t].x, points[t].y);

      c.closePath();

      var _this = this;

      this.draw(c, function () {
        _this.polygonBounds(points, tmp);
        return tmp;
      });
    };
  };

  Tee.Format.prototype.ellipsePath = function (c, cx, cy, width, height) {
    /*
    var w=width*0.5, top=centerY-height, bot=centerY+height;

    c.beginPath();
    c.moveTo(centerX, top);
    c.bezierCurveTo(centerX + w, top, centerX + w, bot, centerX, bot);
    c.bezierCurveTo(centerX - w, bot, centerX - w, top, centerX, top);
    c.closePath();
    */

    if (this.chart.__webgl) {
      c.z = this.z;
      c.depth = this.depth;
      c.ellipsePath(cx, cy, width, height);
    } else {
      c.save();
      c.translate(cx, cy);
      c.scale(width * 0.5, height * 0.5);
      c.beginPath();
      c.arc(0, 0, 1, 0, 2 * Math.PI, false);
      //c.scale(height*0.5,width*0.5);
      c.restore();
    }
  };

  Tee.Format.prototype.ellipse = function (cx, cy, width, height) {
    var c = this.chart.ctx;
    this.ellipsePath(c, cx, cy, width, height);
    this.draw(c, null, cx - width * 0.5, cy - height * 0.5, width, height);
  };

  Tee.Format.prototype.sphere = function (cx, cy, width, height) {
    if (this.chart.__webgl) {
      var ctx = this.chart.ctx;
      ctx.depth = this.depth;
      ctx.z = this.z;

      if (this.gradient.visible)
        ctx.fillStyle = this.gradient.colors[this.gradient.colors.length - 1];

      ctx.sphere(cx, cy, width, height);
    } else this.ellipse(cx, cy, width, height);
  };

  Tee.Format.prototype.cylinder = function (r, topradius, vertical, inverted) {
    if (this.chart.__webgl) {
      var ctx = this.chart.ctx;
      ctx.depth = this.depth;
      ctx.z = this.z;
      ctx.image = this.image;
      ctx.cylinder(r, topradius, vertical, inverted);
      return;
    } else if (topradius == 1) this.cube(r);
    else {
      var w = r.width,
        h = r.height;

      if (vertical)
        this.polygon([
          new Point(r.x + w * 0.5, r.y),
          new Point(r.x, r.y + h),
          new Point(r.x + w, r.y + h),
        ]);
      else
        this.polygon([
          new Point(r.x + w, r.y + h * 0.5),
          new Point(r.x, r.y),
          new Point(r.x, r.y + h),
        ]);
    }
  };

  Tee.Format.prototype.cube = function (r) {
    var a = this.chart.aspect,
      is3D = a.view3d,
      w,
      h,
      old,
      ax = 0,
      ay = 0;

    if (is3D) {
      if (this.chart.__webgl) {
        var ctx = this.chart.ctx;
        ctx.depth = this.depth;
        ctx.z = this.z;
        ctx.cube(r, this.round.x);
        return;
      }

      var z = this.z,
        depth = this.depth;

      (ax = z * a._orthox), (ay = -z * a._orthoy);

      w = r.x + r.width;
      h = r.y + r.height;

      var dx = depth * a._orthox,
        dy = -depth * a._orthoy;

      old = this.shadow.visible;
      this.shadow.visible = false;

      var ww = w + dx,
        hh = r.y + dy;

      if (depth > 0) {
        this.polygon([
          { x: w, y: r.y },
          { x: ww, y: hh },
          { x: ww, y: h + dy },
          { x: w, y: h },
        ]);

        if (r.width > 0)
          this.polygon([
            { x: r.x, y: r.y },
            { x: r.x + dx, y: hh },
            { x: ww, y: hh },
            { x: w, y: r.y },
          ]);
      }
    }

    this.rectPath(r.x + ax, r.y + ay, r.width, r.height);

    if (is3D) this.shadow.visible = old;
  };

  Tee.Format.prototype.rectPath = function (x, y, width, height) {
    var c = this.chart.ctx;

    c.beginPath();

    if (this.round.x > 0 && this.round.y > 0)
      this.roundRect(c, x, y, width, height);
    else c.rect(x, y, width, height);
  };

  /**
   * @private
   */
  Tee.Format.prototype.setChart = function (chart) {
    this.chart = chart;
    this.shadow.chart = chart;
    this.gradient.chart = chart;
    this.font.setChart(chart);
    if (this._img) this._img.chart = chart;
    this.stroke.setChart(chart);
  };

  /**
   * @constructor
   * @augments Tee.Tool
   * @class Represents a rectangle containing text
   * @param {Tee.Chart} chart The parent chart this annotation belongs to.
   * @param {String} text The text to draw inside the annotation.
   * @param {Number} [x=10] Optional left side position in pixels.
   * @param {Number} [y=10] Optional top side position in pixels.
   * @property {Tee.Margins} margins Properties to control spacing between text and rectangle boundaries.
   * @property {Boolean} visible When true, the annotation is displayed.
   * @property {Boolean} transparent When true, the annotation background is not displayed. Only the text is painted.
   * @property {Tee.Format} format Properties to control the annotation background and text appearance.
   */
  Tee.Annotation = function (chart, text, x, y) {
    Tee.Tool.call(this, chart);

    /**
     * @property {Tee.Point} position Top-left coordinates of annotation rectangle.
     * @default x:10, y:10
     */
    this.position = new Point(x || 10, y || 10);

    var m = (this.margins = new Margins());

    this.items = [];

    var b = (this.bounds = new Rectangle());
    this.visible = true;
    this.transparent = false;
    this.text = text || "";
    this.isDom = false;
    this.domElement = null;
    this.domStyle =
      "border-radius: 5px;border: 2px solid #faad44;background: #FFF;padding:5px;";
    var f = (this.format = new Tee.Format(chart));

    f.font.textAlign = "center";
    f.font.baseLine = "top";
    f.fill = "beige";
    f.round = { x: 4, y: 4 };
    f.stroke.fill = "silver";
    f.shadow.visible = true;

    f.depth = 0.05;
    f.z = 0.5;

    var fontH, thisH, over;

    this.getDOMHeight = function () {
      return this.domElement == null ? 0 : this.domElement.offsetHeight;
    };
    this.getDOMWidth = function () {
      return this.domElement == null ? 0 : this.domElement.offsetWidth;
    };
    this.moveTo = function (x, y) {
      this.position.x = x;
      this.position.y = y;

      this.resize();
    };

    function isEmpty(str) {
      return !str || 0 === str.length;
    }

    this.shouldDraw = function () {
      return this.visible && !isEmpty(this.text);
    };

    this.resize = async function () {
      if (isEmpty(this.text)) return;

      f.font.prepare();

      this.rows = this.text.split("\n");
      fontH = f.textHeight(this.text);

      var l = this.rows.length;

      thisH = fontH * l + m.top;

      var w,
        h = thisH + m.bottom;

      if (l > 1) {
        w = 0;
        while (l--) w = Math.max(w, f.textWidth(this.rows[l] + "W"));
      } else w = f.textWidth(this.text + "W");

      w += m.left + m.right;

      var pos = this.position,
        p = pos.y + thisH,
        t,
        i;
      /*
    for(t=0; i=this.items[t++];)
    {
      var bi=i.bounds;
      i.resize();
      h+=bi.height;
      w=Math.max(w,bi.width);
      bi.x=pos.x;
      bi.y=p;
      p+=bi.height;
    }

    for(t=0; i=this.items[t++];)
      i.bounds.width=w-m.right;
      */
      b.set(pos.x, pos.y, w, h);
    };

    this.add = function (text) {
      var a = new Tee.Annotation(this.chart, text);

      this.items.push(a); //[this.items.length]=a;
      a.transparent = true;
      return a;
    };

    this.doDraw = async function () {
      if (!isEmpty(this.text)) {
        if (this.isDom) {
          await this.drawDOMText();
        } else {
          if (this.transparent) this.chart.ctx.z = f.z;
          else if (this.chart.aspect.view3d && this.format.depth > 0) {
            var oldz = f.z;
            f.z -= this.format.depth * 0.5;
            f.cube(b);
            f.draw(this.chart.ctx, null, b);
            f.z = oldz;
          } else {
            this.chart.ctx.z = f.z;
            f.rectangle(b);
          }

          var old,
            ft = this.format.transparency,
            ctx = this.chart ? this.chart.ctx : null;

          if (ft > 0) {
            old = ctx.globalAlpha;
            ctx.globalAlpha = (1 - ft) * old;
          }

          f.font.prepare();

          b.y += m.top + 0.1 * fontH;
          b.x += m.left;

          var w = b.width;
          b.width -= m.right;

          /*
      if (this.transform) {
       ctx.save();
       this.transform(b);
      }
      */

          f.drawText(b, this.text);

          //if (this.transform) ctx.restore();

          b.x = this.position.x;
          b.y = this.position.y;
          b.width = w;

          if (ft > 0) ctx.globalAlpha = old;
        }
        /*
    for(var t=0, i; i=this.items[t++];)
       i.doDraw();
       */
      }
    };

    /**
     * @returns {Boolean} Returns if {@link Tee.Point} p is inside this Annotation bounds.
     */
    this.clicked = function (p) {
      return this.visible && b.contains(p); // (&& this.text!="")
    };

    this.doMouseMove = function (p) {
      this.mouseinside = this.clicked(p);

      if (this.mouseinside) {
        if (this.cursor) this.chart.newCursor = this.cursor;

        if (!this.wasinside) over = new AnimateHover(250, b, f);
      } else if (this.wasinside) {
        over.restore();
        this.chart.draw();
      }

      this.wasinside = this.mouseinside;
    };

    this.mousemove = function (p) {
      if (this.cursor && this.cursor != "default") this.doMouseMove(p);
    };

    this.forceDraw = function () {
      this.resize();
      this.doDraw();
    };

    this.setChart = function (chart) {
      this.chart = chart;
      this.format.setChart(chart);
    };

    this.drawDOMText = async function () {
      if (this.domElement) await this.resize(); //when awaited, actions a correct update on DOM text (!)

      var rect = this.chart.canvas.getBoundingClientRect();
      var opacity = this.transparent ? "opacity:0;" : "opacity:1;";
      if (!this.domElement) {
        this.domElement = document.createElement("div");
        document.body.appendChild(this.domElement);
      }
      //this.chart.canvas.setAttribute('style', 'z-index:1;');
      if (this.visible == false)
        this.domElement.setAttribute(
          "style",
          "visibility:hidden; position:absolute;top:" +
            (this.position.y + rect.top) +
            "px;left:" +
            (this.position.x + rect.left) +
            "px;display:block;z-index:10000;" +
            this.domStyle +
            opacity
        );
      else
        this.domElement.setAttribute(
          "style",
          "visibility:visible; position:absolute;top:" +
            (this.position.y + rect.top) +
            "px;left:" +
            (this.position.x + rect.left) +
            "px;display:block;z-index:10000;" +
            this.domStyle +
            opacity
        );
      if (this.domElement.innerHTML != this.text)
        this.domElement.innerHTML = this.text;
    };
  };

  Tee.Annotation.prototype = new Tee.Tool();

  Tee.Annotation.prototype.draw = async function () {
    if (this.isDom) await this.drawDOMText();
    else if (this.visible) this.forceDraw();
  };

  /**
   * @constructor
   * @augments Tee.Tool
   * @class Allows dragging series data by mouse or touch
   * @param {Tee.Chart} chart The parent chart this tool belongs to.
   * @property {Tee.Series} [series=null] A series to be dragged, or null to drag all.
   */
  Tee.DragTool = function (chart) {
    Tee.Tool.call(this, chart);

    this.series = null;

    var ta = (this.target = { series: null, index: -1 });

    this.clicked = function () {
      ta.series = null;
      ta.index = -1;
    };

    var p = new Point(0, 0);

    this.Point = p;

    this.mousedown = function (event) {
      var s = this.chart.series.items,
        t,
        len = s.length;

      this.chart.calcMouse(event, p);

      ta.series = null;
      ta.index = -1;

      if (this.series && this.series.visible) {
        ta.index = this.series.clicked(p);
        if (ta.index != -1) ta.series = this.series;
      } else
        for (t = 0; t < len; t++) {
          if (s[t].visible) {
            ta.index = s[t].clicked(p);
            if (ta.index != -1) {
              ta.series = s[t];
              break;
            }
          }
        }

      return ta.index != -1;
    };

    this.mousemove = function (p) {
      if (ta.index != -1) {
        var s = ta.series,
          tmp = s.mandatoryAxis.fromPos(s.yMandatory ? p.y : p.x);

        if (this.onchanging) tmp = this.onchanging(this, tmp);

        s.data.values[ta.index] = tmp;

        if (this.onchanged) this.onchanged(this, tmp);
        this.chart.draw();
      }
    };
  };

  Tee.DragTool.prototype = new Tee.Tool();

  /**
   * @constructor
   * @augments Tee.Tool
   * @class Draws mouse draggable horizontal and / or vertical lines inside axes
   * @param {Tee.Chart} chart The parent chart this cursor tool belongs to.
   * @property {String} direction Determines if the cursor will be displayed as "vertical", "horizontal" or "both".
   * @property {Tee.Format} format Properties to control the cursor lines stroke appearance.
   * @property {Tee.Point} size The size of cursor, {x:0, y:0} means lines will cover full axes bounds.
   * @property {boolean} followMouse When true the cursor follows mouse movement over the chart.
   */
  Tee.CursorTool = function (chart) {
    Tee.Tool.call(this, chart);

    this.direction = "both"; // "vertical", "horizontal", "both"
    this.size = new Point(0, 0);

    this.followMouse = true;
    this.dragging = -1;

    this.format = new Tee.Format(chart);

    this.horizAxis = chart ? chart.axes.bottom : null;
    this.vertAxis = chart ? chart.axes.left : null;

    var old,
      r = new Rectangle();

    this.over = function (p) {
      var res = -1;
      if (r.contains(p)) {
        var v = Math.abs(old.x - p.x) < 3,
          h = Math.abs(old.y - p.y) < 3,
          d = this.direction;
        if (d == "both" && v && h) res = 0;
        else if (v && (d == "both" || d == "vertical")) res = 1;
        else if (h && (d == "both" || d == "horizontal")) res = 2;
      }
      return res;
    };

    this.calcRect = function () {
      var cr = chart.chartRect,
        h = this.horizAxis,
        v = this.vertAxis;

      r.x = h ? h.startPos : cr.x;
      r.width = h ? h.endPos - r.x : cr.width;

      r.y = v ? v.startPos : cr.y;
      r.height = v ? v.endPos - r.y : cr.height;
    };

    var pp = new Point(0, 0);

    this.mousedown = function (p) {
      this.chart.calcMouse(p, pp);
      this.dragging = this.followMouse ? -1 : this.over(pp);
      return this.dragging > -1;
    };

    this.clicked = function () {
      this.dragging = -1;
    };

    this.mousemove = function (p) {
      var d = this.dragging,
        fm = this.followMouse;

      if (fm || d > -1) {
        if (!old) old = new Point();

        if (old.x != p.x || old.y != p.y) {
          this.calcRect();

          if (r.contains(p)) {
            if (fm || d === 0 || d === 1) old.x = p.x;
            if (fm || d === 0 || d === 2) old.y = p.y;

            if (this.render == "full") this.chart.draw();
            else {
              //Restore initial canvas before drawing cursor to clean previous position

              if (canvasCopy)
                if (this.render == "copy")
                  this.chart.ctx.drawImage(canvasCopy, 0, 0);
                else {
                  var b = chart.bounds;
                  ctxCopy.clearRect(b.x, b.y, b.width, b.height);
                }

              this.dodraw(this.render == "copy" ? this.chart.ctx : ctxCopy);
            }

            if (this.onchange) this.onchange(p);

            return;
          }
        }
      }

      var o = this.over(p);

      if (old && o > -1) {
        this.chart.newCursor =
          o === 0 ? "move" : o === 1 ? "e-resize" : "n-resize";
      } else this.chart.newCursor = "default";
    };

    this.render = "copy";

    var canvasCopy, ctxCopy;

    this.setRender = function (r) {
      this.render = r;

      if (canvasCopy) {
        this.resetCopy();
        this.chart.draw();
      }
    };

    this.resetCopy = function () {
      var ca = this.chart.canvas;

      if (this.render == "layer") {
        canvasCopy.style.position = "absolute";
        ca.parentNode.appendChild(canvasCopy);

        canvasCopy.setAttribute("left", ca.offsetLeft + "px");
        canvasCopy.setAttribute("top", ca.offsetTop + "px");

        canvasCopy.style.left = ca.offsetLeft;
        canvasCopy.style.top = ca.offsetTop;

        canvasCopy.style.zIndex = 10;
        canvasCopy.style.pointerEvents = "none";
      } else if (canvasCopy.parentNode)
        canvasCopy.parentNode.removeChild(canvasCopy);
    };

    this.draw = function () {
      if (this.render == "full") this.dodraw(this.chart.ctx);
      else {
        if (!canvasCopy) {
          canvasCopy = this.chart.canvas.cloneNode();
          this.resetCopy();
          ctxCopy = canvasCopy.getContext("2d"); // ,{alpha:false} (opaque canvas)
        }

        if (this.render == "copy") {
          //var b=chart.bounds;
          //ctxCopy.clearRect(b.x,b.y,b.width,b.height);
          ctxCopy.drawImage(chart.canvas, 0, 0);

          this.dodraw(this.chart.ctx);
        } else this.dodraw(ctxCopy);
      }
    };

    this.dodraw = function (c) {
      var d = this.direction,
        both = d == "both",
        p;

      this.calcRect();

      if (!old) old = new Point(r.x + 0.5 * r.width, r.y + 0.5 * r.height);

      c.beginPath();

      if (both || d == "vertical") {
        p = this.size.y * 0.5;
        c.moveTo(old.x, p === 0 ? r.y : old.y - p);
        c.lineTo(old.x, p === 0 ? r.y + r.height : old.y + p);
      }

      if (both || d == "horizontal") {
        p = this.size.x * 0.5;
        c.moveTo(p === 0 ? r.x : old.x - p, old.y);
        c.lineTo(p === 0 ? r.x + r.width : old.x + p, old.y);
      }

      this.format.stroke.prepare(this.format.stroke.fill, c);
      c.stroke();
    };
  };

  Tee.CursorTool.prototype = new Tee.Tool();

  /**
   * @constructor
   * @augments Tee.Tool
   * @class Shows an annotation when mouse is over a series data point
   * @param {Tee.Chart} chart The parent chart this tooltip belongs to.
   * @property {Boolean} [autoHide=false] When true, the tooltip is automatically removed after "delay" milliseconds.
   * @property {Number} [delay=1000] Amount of milliseconds to wait before removing the last displayed tooltip (when "autoHide" is true).
   * @property {Number} [animated=100] Duration in milliseconds to animate the movement of tooltip from old to new position.
   * @property {Boolean} [findPoint=false] When true, the tooltip jumps to the nearest point.
   * @property {Boolean} [realTime=false] When true, unmoved tip provides live updates on changing point values.
   */
  Tee.ToolTip = function (chart) {
    Tee.Annotation.call(this, chart);
    this.pointer = {
      fill: "Green",
      firstCircleRadius: "2",
      secondCircleRadius: "5",
      visible: false,
      firstCircleOpacity: "1",
      secondCircleOpacity: "0.4",
      animationVisible: true,
      animationDuration: 200,
    };
    this.visible = false;
    this.findPoint = false;

    /**
     * @private
     */
    this.currentSeries = null;
    /**
     * @private
     */
    this.currentIndex = -1;

    this.realTime = false;
    /**
     * @private
     */
    this.timID = null;

    this.autoHide = false;
    this.delay = 1000;
    this.animated = 100;
    this.autoRedraw = true;
    this.render = "dom";
    this.domStyle =
      "padding:5px; margin-left:5px; background-color:#FFF; border-radius:4px 4px; color:#222;";

    this.hide = async function () {
      var isDom = this.render === "dom";

      if (this.visible || isDom) {
        if (this.onhide) this.onhide(this);

        this.visible = false;

        if (this.autoRedraw)
          if (isDom) Tee.DOMTip.hide();
          else this.chart.draw();

        this.currentIndex = -1;
        this.currentSeries = null;
      }
    };

    var redraw = function (args) {
      if (args) args[0].hide();
    };

    this.mousemove = async function (p) {
      var li = this.chart.series,
        len = li.count(),
        ser = null,
        index = -1;
      if (this.chart.chartRect.contains(p))
        for (var t = len - 1; t >= 0; t--) {
          var s = li.items[t];

          if (s.visible) {
            index = s.clicked(p);
            if (index == -1 && s.continuous) {
              index = Math.round(
                this.chart.axes.bottom.fromSizeCalcIndex(
                  p.x - this.chart.axes.bottom.startPos
                )
              );
              var distance, oldDistance;
              for (var n = 0; n < len; n++) {
                distance = Math.abs(
                  li.items[n].data.values[index] -
                    this.chart.axes.left.fromPos(p.y)
                );
                if (n == 0) {
                  oldDistance = distance;
                  s = li.items[n];
                } else if (distance < oldDistance) {
                  s = li.items[n];
                }
              }
            }

            if (index != -1) {
              ser = s;
              this.currP = p;
              break;
            }
          }
        }
      else {
        index = -1;
        this.currP = undefined;
      }
      if (index == -1) {
        this.currP = undefined;
        this.hide();

        this.currentIndex = -1;
        this.currentSeries = null;
      } else if (
        index != this.currentIndex ||
        ser != this.currentSeries ||
        this.realTime
      ) {
        this.currentIndex = index;
        this.currentSeries = ser;

        if (ser) {
          await this.refresh(ser, index);

          if (this.autoHide && this.delay > 0) {
            clearTimeout(this.timID);
            this.timID = await window.setTimeout(redraw, this.delay, [this]);
          }
        }
      }
    };

    var o = null;

    function step() {
      function changeTo(f) {
        o.moveTo(o.oldX + f * o.deltaX, o.oldY + f * o.deltaY);
        if (o.autoRedraw) o.chart.draw();
      }

      var now = new Date().getTime(),
        f = (now - o.init) / o.animated;

      if (f < 1) {
        changeTo(f);
        window.requestAnimFrame(step, o);
      } else changeTo(1);
    }

    this.refresh = async function (series, index) {
      var isDom = this.render === "dom";
      this.visible = !isDom;

      this.text = series.markText(index);

      if (this.ongettext)
        this.text = await this.ongettext(this, this.text, series, index);

      if (this.text !== "") {
        this.resize();

        var p = new Point();
        series.calc(index, p);

        p.x -= this.bounds.width * 0.5;
        p.y -= 1.5 * this.bounds.height;

        if (p.x < 0) p.x = 0;
        if (p.y < 0) p.y = 0;

        if (
          !isDom &&
          !this.autoHide &&
          this.animated > 0 &&
          !isNaN(this.position.x) &&
          !isNaN(this.position.y)
        ) {
          this.oldX = this.position.x;
          this.oldY = this.position.y;

          this.deltaX = p.x - this.oldX;
          this.deltaY = p.y - this.oldY;

          this.init = new Date().getTime();
          o = this;
          window.requestAnimFrame(step, this);
        } else {
          this.moveTo(p.x, p.y);

          if (this.autoRedraw)
            if (isDom)
              Tee.DOMTip.show(
                this.text,
                "auto",
                this.chart.canvas,
                this.domStyle,
                this
              );
            else this.chart.draw();
        }

        if (this.onshow) this.onshow(this, series, index);
      }
    };
  };

  Tee.ToolTip.prototype = new Tee.Annotation();

  /**
   * @memberOf Tee.Chart
   * @constructor
   * @class Contains a list with all "tools"
   * @param {Tee.Chart} chart The parent chart this tool list belongs to.
   * @property {Tee.Tool[]} items Array of Tee.Tool objects.
   */
  function Tools(chart) {
    this.chart = chart;
    this.items = [];

    this.draw = function () {
      for (var t = 0, s; (s = this.items[t++]); )
        if (s.active)
          if (s instanceof Tee.ToolTip) {
            if (s.currP != undefined && s.realTime) s.mousemove(s.currP);
            else s.draw();
          } else s.draw();
    };

    this.mousemove = function (p) {
      for (var t = 0, s; (s = this.items[t++]); )
        if (s.active) {
          s.currP = undefined;
          s.mousemove(p);
        }
    };

    this.mousedown = function (event) {
      for (var t = 0, s, done = false; (s = this.items[t++]); )
        if (s.active) {
          if (s.mousedown(event)) done = true;
        }

      return done;
    };

    this.mouseout = function () {
      for (var t = 0, s; (s = this.items[t++]); ) if (s.active) s.mouseout();
    };

    this.clicked = function (p) {
      var l = this.items.length;

      for (var t = l, s, done = false; (s = this.items[--t]); ) {
        if (s.active && s.clicked(p)) {
          done = true;

          if (s.onclick) done = s.onclick(s, p.x, p.y);
        }
      }

      return done;
    };

    /**
     * @returns {Tee.Tool} Returns the tool parameter.
     */
    this.add = function (tool) {
      this.items.push(tool);
      return tool;
    };
  }

  // http://simple.wikipedia.org/wiki/Rainbow
  Tee.RainbowPalette = function () {
    return [
      "#FF0000",
      "#FF7F00",
      "#FFFF00",
      "#00FF00",
      "#0000FF",
      "#6600FF",
      "#8B00FF",
    ];
  };

  /**
   * @constructor
   * @class Contains an array of colors to be used as series data fill color
   * @param {Color[]} colors The array of colors to build the palette.
   */
  Tee.Palette = function (colors) {
    this.colors = colors;
  };

  /**
   * @returns {String} Returns the index'th color in colors array (mod length
   * if index is greater than number of colors).
   * @param {Integer} index The position inside colors array (circular, if index is greater than colors length).
   */
  Tee.Palette.prototype.get = function (index) {
    return this.colors[index == -1 ? 0 : index % this.colors.length];
  };

  /**
   * @constructor
   * @memberOf Tee.Chart
   * @class Controls how to zoom chart axes by mouse or touch drag
   * @param {Tee.Chart} chart The parent chart this zoom object belongs to.
   * @property {Boolean} enabled Allows chart zoom by mouse/touch dragging.
   * @property {Number} mouseButton Defines the mouse button that can be used to zoom (0=Left button, etc).
   * @property {Tee.Format} format Properties to control the appearance of rectangle that appears while dragging.
   * @property {String} [direction="both"] Allows chart zoom in horizontal, vertical or both directions.
   */
  function Zoom(chart) {
    this.chart = chart;

    /**
     * @private
     */
    this.active = false;

    this.enabled = true;

    /**
     * @private
     */
    this.done = false;

    this.touching = false;
    this.direction = "both";

    /**
     * When true, zoom rectangle maintains same width to height proportion of Chart.
     */
    this.keepAspect = false;

    this.mouseButton = 0;
    this.wheel = { enabled: false, factor: 1 };

    var f = (this.format = new Tee.Format(chart));
    f.fill = "rgba(255,255,255,0.5)";
    f.stroke.fill = "darkgray";
    f.stroke.size = 2;

    //c.ctx.globalCompositeOperation="source-over";

    var r = new Rectangle();

    this.change = function (pos) {
      if (!this.old) this.old = new Point();

      var old = this.chart.oldPos;

      this.old.x = pos.x - old.x;

      if (this.keepAspect) {
        var r = this.chart.chartRect;
        this.old.y = this.old.x * (r.height / r.width);
      } else this.old.y = pos.y - old.y;
    };

    function check(z) {
      var c = z.chart.chartRect,
        d = z.direction,
        b = d === "both";
      r.set(c.x, c.y, c.width, c.height);

      if (z.old) {
        if (b || d === "horizontal") {
          if (z.old.x < 0) {
            r.x = z.chart.oldPos.x + z.old.x;
            r.width = -z.old.x;
          } else {
            r.x = z.chart.oldPos.x;
            r.width = z.old.x;
          }
        }

        if (b || d === "vertical") {
          if (z.old.y < 0) {
            r.y = z.chart.oldPos.y + z.old.y;
            r.height = -z.old.y;
          } else {
            r.y = z.chart.oldPos.y;
            r.height = z.old.y;
          }
        }
      }

      return r;
    }

    this.draw = function () {
      f.rectangle(check(this));
    };

    this.apply = function () {
      if (this.old.x < 0 || this.old.y < 0) {
        this.reset();

        if (this.onreset) this.onreset();
      } else {
        check(this);

        if (r.width > 3 && r.height > 3) {
          var d = this.direction,
            b = d === "both";

          this.chart.axes.each(function () {
            if (this.horizontal) {
              if (b || d === "horizontal") this.calcMinMax(r.x, r.x + r.width);
            } else if (b || d === "vertical") this.calcMinMax(r.y + r.height, r.y);
          });

          return true;
        }
      }

      return false;
    };

    this.reset = function () {
      this.chart.axes.each(function () {
        this.automatic = true;
      });
    };
  }

  /**
   * @constructor
   * @memberOf Tee.Chart
   * @class Controls how to scroll chart axes by mouse or touch drag
   * @param {Tee.Chart} chart The parent chart this scroll object belongs to.
   * @property {Boolean} [enabled=true] Allows chart scroll by mouse/touch dragging.
   * @property {Number} [mouseButton=2] Defines the mouse button that can be used to scroll (2=Right button, etc).
   * @property {String} [direction="both"] Determines if scroll is allowed in "horizontal", "vertical" or "both" directions.
   */
  function Scroll(chart) {
    this.chart = chart;

    /**
     * @private
     */
    this.active = false;

    this.enabled = true;

    /**
     * @private
     */
    this.done = false;

    this.mouseButton = 2;
    this.direction = "both"; // horizontal,vertical,both

    /**
     * @private
     */
    this.position = new Point(0, 0);
  }

  /**
   * @memberOf Tee.Chart
   * @constructor
   * @augments Tee.Annotation
   * @class Displays text at top or bottom chart sides
   * @param {Tee.Chart} chart The parent chart this title object belongs to.
   * @param {Color} fontColor The color to fill the title text.
   * @param {Boolean} [expand=false] When true, title background is aligned to panel.
   */
  function Title(chart, fontColor) {
    Tee.Annotation.call(this, chart);
    this.transparent = true;

    this._expand = false;

    if (obDefP)
      obDefP(this, "expand", {
        get: function () {
          return this._expand;
        },
        set: function (value) {
          this._expand = value;
          if (!this._expand) {
            var ff = this.format;
            ff.round.x = 8;
            ff.round.y = 8;
            ff.round.corners = null;
            ff.stroke.fill = "black";
          }
        },
      });
    else this._expand = this.expand = false;

    var f = this.format.font,
      s = f.shadow,
      p = this.position;
    s.visible = true;
    s.width = 2;
    s.height = 2;
    s.blur = 8;

    f.style = "18px Tahoma";
    f.fill = fontColor;

    this.padding = 4;

    this.calcRect = function (fromTop) {
      this.resize();

      var h = this.transparent ? 1 : 2,
        b = this.bounds,
        size = b.height + h * this.padding,
        r = chart.chartRect;

      if (fromTop) {
        p.y = r.y;

        if (r.automatic) r.setTop(r.y + size);
      } else {
        p.y = r.y + r.height - b.height - this.padding;

        if (r.automatic) r.height -= size;
      }

      if (r.height < 0) r.height = 0;

      p.x = 0.5 * (chart.canvas.width - b.width);
    };

    this.tryDraw = function (top) {
      if (this.shouldDraw()) {
        this.calcRect(top);

        var b = this.bounds,
          ctx = this.chart.ctx,
          groups = ctx.beginParent;

        this.visual = groups ? ctx.beginParent() : null;

        if (this._expand) {
          var f = chart.panel.format;
          p.x = f.stroke.fill !== "" ? f.stroke.size : 0;
          p.y = top
            ? p.x
            : chart.canvas.height -
              (f.shadow.visible ? f.shadow.height : 0) -
              p.x -
              b.height; //+1; <-- only when panel border round?

          b.width =
            chart.canvas.width -
            (f.shadow.visible ? f.shadow.width : 0) -
            2 * p.x;

          var ff = this.format;
          ff.round.x = f.round.x;
          ff.round.y = f.round.y;
          ff.round.corners = [top, top, !top, !top];
          ff.stroke.fill = "";

          this.transparent = false;
        }

        b.x = p.x;
        b.y = p.y;

        this.doDraw();

        if (groups) ctx.endParent();
      }
    };
  }

  Title.prototype = new Tee.Annotation();

  /**
   * @memberOf Tee.Chart
   * @constructor
   * @public
   * @class Defines the visual properties for chart background
   * @param {Tee.Chart} chart The parent chart this panel object belongs to.
   * @property {Tee.Margins} margins Controls the spacing between background panel to chart contents.
   * @property {Tee.Format} format Visual properties to paint the chart panel background.
   * @property {Boolean} transparent Determines if panel background will be filled or not.
   */
  function Panel(chart) {
    var f = (this.format = new Tee.Format(chart));
    f.round.x = 12;
    f.round.y = 12;
    f.stroke.size = 3;
    f.gradient.visible = true;
    f.gradient.direction = "bottomtop";
    f.shadow.visible = true;
    f.stroke.fill = "#606060";

    this.transparent = !!chart.__webgl;

    this.margins = new Margins();

    this.clear = function () {
      var b = chart.bounds;
      chart.ctx.clearRect(b.x, b.y, b.width, b.height);
    };

    this.draw = function () {
      if (this.transparent || chart.__webgl) this.clear();
      else {
        var r = chart.chartRect,
          sh = f.shadow;

        if (sh.visible) {
          r.width -= 0.5 * Math.abs(sh.width) + 2;
          r.height -= 0.5 * Math.abs(sh.height) + 2;

          if (sh.width < 0) r.x -= sh.width;
          if (sh.height < 0) r.y -= sh.height;
        }

        var s = 0;

        if (f.stroke.fill !== "") {
          s = f.stroke.size;
          if (s > 1) {
            s *= 0.5;
            r.x += s;
            r.y += s;
            r.width -= 2 * s;
            r.height -= 2 * s;
          }
        }

        if (sh.visible || f.round.x > 0 || f.round.y > 0) this.clear();

        f.rectangle(r);

        if (s > 0) {
          r.x += s;
          r.y += s;
          r.width -= 2 * s;
          r.height -= 2 * s;
        }
      }
    };
  }

  /**
   * @memberOf Tee.Chart
   * @constructor
   * @public
   * @class Properties to display a rectangle panel around chart axes
   * @param {Tee.Chart} chart The parent chart this wall object belongs to.
   * @property {Tee.Format} format Defines visual properties to paint this wall.
   * @property {Boolean} [visible=true] Determines if this wall will be displayed or not.
   */
  function Wall(chart) {
    var f = (this.format = new Tee.Format(chart));
    f.fill = "#E6E6E6";
    f.stroke.fill = "black";
    f.z = 0;
    f.depth = 0;

    this.visible = true;
    this.bounds = new Rectangle();
    this.size = 0;

    this.draw = function () {
      f.cube(this.bounds);
      f.draw(chart.ctx, null, this.bounds);
    };
  }

  /**
   * @returns {Number} Returns the integer part of value, without decimals, rounded to lower.
   */
  function trunc(value) {
    return value | 0;
  }

  /**
   * @memberOf Tee.Chart
   * @constructor
   * @class Defines a scale from minimum to maximum, to transform series points into chart canvas pixels coordinates.
   * @param {Tee.Chart} chart The chart object this axis object belongs to.
   * @param {Boolean} horizontal Determines if axis is horizontal or vertical.
   * @param {Boolean} otherSide Determines if axis is at top/right or bottom/left side of chart.
   * @property {Tee.Format} format Visual properties to draw the axis line.
   * @property {Tee.Chart.Axis-Labels} labels Properties to display axis labels at tick increments.
   * @property {Tee.Chart.Axis-Grid} grid Properties to display grid lines at tick increments.
   * @property {Tee.Chart.Axis-Ticks} ticks Properties to display tick lines at each increment.
   * @property {Tee.Chart.Axis-Ticks} innerTicks Properties to display tick lines at each increment, inside chart.
   * @property {Tee.Chart.Axis-Ticks} minorTicks Properties to display small tick lines between ticks.
   * @property {Tee.Chart.Axis-Title} title Properties to display text that describes the axis.
   */
  function Axis(chart, horizontal, otherSide) {
    this.chart = chart;
    this.visible = true;
    this.inverted = false;
    this.horizontal = horizontal;
    this.otherSide = otherSide;
    this.bounds = new Rectangle();

    this.position = 0;

    this.format = new Tee.Format(chart);
    this.format.stroke.size = 2;
    this.format.depth = 0.2;

    this.custom = false;

    this.z = otherSide ? 1 : 0;

    this.maxLabelDepth = 0;

    /**
     * @constructor
     * @public
     * @class Displays text to annotate axes
     * @param {Tee.Chart} chart The chart object this axis labels object belongs to.
     * @property {Tee.Format} format Defines the visual properties to paint the axis title.
     * @property {Boolean} [visible=true] Determines if axis labels will be displayed or not.
     * @property {String} dateFormat="shortDate" Configures string format for date & time labels
     * @property {Number} rotation=0 Defines the label rotation angle from 0 to 360 degree.
     * @property {String} labelStyle="auto" Determines label contents from series data ("auto", "value", "mark", "text").
     * @property {Number} decimals=2 Defines the number of decimals for floating-point numeric labels.
     * @property {Boolean} alternate=false When true, labels are displayed at alternate positions to fit more labels in the same space.
     * @property {String} wordWrap="no" Determines if replace label white spaces with new line ("auto", "yes", "no").	 
     */
    function Labels(chart, axis) {
      this.chart = chart;
      this.format = new Tee.Format(chart);
      this.decimals = 2;
      this.fixedDecimals = false;
      this.padding = 4;
      this.separation = 10; // %
      this.visible = true;
      this.rotation = 0;
      this.alternate = false;
      this.maxWidth = 0;

      this.wordWrap = "no"; // auto,yes,no

      this.roundFirst = true;
      this.labelStyle = "auto"; // auto,value,mark,text

      this.dateFormat = "shortDate";

      this.checkStyle = function () {
        var st = this.labelStyle,
          s = axis.firstSeries;

        this._text = null;
        this._textlabels = null;

        if (st == "auto") {
          if (
            s.data.labels.length > 0 &&
            s.associatedToAxis(axis) &&
            axis.horizontal == s.notmandatory.horizontal
          ) {
            this._text = s;

            /*    if ((this.chart.series.items.length > 1) && (this.chart.series.items[0] instanceof Tee.Bar) 
                                    && (this.chart.series.items[0].stacked == "sideAll")){
        
				var li=chart.series.items, t, tt, ser;
				this._textlabels=this._text.data.labels;
				for(t=1; ser=li[t++];){
				   if (ser.visible && ser.associatedToAxis(axis)) {
					 for(tt=0; tt < ser.data.values.length; tt++){  
					   this._textlabels.push(ser.data.labels[tt]);
				     }
			       }
                }

                if (s === undefined)
                  s="";
            }*/
          }
        } else if (st == "mark" || st == "text") this._text = s;
      };

      this.formatValueString = function (value) {
        if (this.valueFormat) {
          var DecimalSeparator = Number("1.2").toLocaleString().substr(1, 1);

          var AmountWithCommas = (value * 1).toLocaleString();
          var arParts = String(AmountWithCommas).split(DecimalSeparator);
          var intPart = arParts[0];

          var padding = "";
          if (this.decimals > 0)
            for (var i = 0; i < this.decimals; i++) {
              padding = padding + "0";
            }

          var decPart = arParts.length > 1 ? arParts[1] : "";
          decPart = (decPart + padding).substr(0, this.decimals);

          if (decPart.length > 0) return intPart + DecimalSeparator + decPart;
          else return intPart;
        } else return value.toFixed(this.decimals);
      };

      /**
       * @returns {String} Returns the series label that corresponds to a given value, (or the value if no label exists).
       */
      this.getLabel = function (value) {
        var v = trunc(value),
          s,
          data;

        if (this._text && v == value) {
          data = this._text.data;

          if (data.x) v = data.x.indexOf(v);

          var li = chart.series.items,
            t,
            ser;

          //specific case: check for non-std sideAll labelling
          if (
            li.length > 0 &&
            li[0] instanceof Tee.Bar &&
            li[0].stacked == "sideAll"
          ) {
            s = data.labels[value];
          } else {
            s = data.labels[v];
          }

          // Last resort, try to find labels from any series in axis:

          if (!s) {
            for (t = 0; (ser = li[t++]); )
              if (ser != this._text) {
                if (ser.visible && ser.associatedToAxis(axis)) {
                  s = ser.data.labels[v];
                  if (s) break;
                }
              }

            if (s === undefined) s = "";
          }
        } else if (axis.dateTime) {
          if (Date.prototype.format)
            // script: src/date.format.js
            s = new Date(value).format(this.dateFormat);
          else s = new Date(value).toDateString(); // fallback
        } else s = this.formatValueString(value);
		
		if ((this.wordWrap != "no") && (this.wordWrap != false)) {      
		  s = s.replace(/ /g, "\n");
	    }

        if (this.ongetlabel) {
          s = this.ongetlabel(value, s);
          this.format.font.prepare(); // <-- in case user code at ongetlabel event has changed the font.
        }

        return "" + s;
      };

      /**
       * @returns {Number} Returns the width in pixels of value converted to string.
       */
      this.width = function (value) {
        var oldFixDec = this.fixedDecimals; //check widest label when sizing
        this.fixedDecimals = true;
        var tmpWidth = this.format.textWidth(this.getLabel(value));
        this.fixedDecimals = oldFixDec;
        return tmpWidth;
      };
    }

    this.labels = new Labels(chart, this);
    var f = this.labels.format.font;

    /**
     * Changes the axis maximum and minimum values
     * @param {Number} delta The positive or negative amount to scroll.
     */
    this.scroll = function (delta) {
      this.automatic = false;
      if (this.inverted) delta = -delta;
      this.minimum += delta;
      this.maximum += delta;
    };

    if (horizontal) {
      f.textAlign = "center";
      f.baseLine = otherSide ? "bottom" : "top";
    } else {
      f.textAlign = otherSide ? "left" : "right";
      f.baseLine = "middle";
    }

    /**
     * @constructor
     * @class Format and parameters to display a grid of lines at axes increments
     * @param {Tee.Chart} chart The chart object this axis grid object belongs to.
     * @property {Tee.Format} format Visual properties to draw axis grids.
     * @property {Boolean} [visible=true] Determines if grid lines will be displayed or not.
     * @property {Boolean} [centered=false] Determines if grid lines are displayed at axis label positions or at middle between labels.
     * @property {Boolean} [lineDash=false] Draws grid lines using dash-dot segments or solid lines.
     */
    function Grid(chart) {
      this.chart = chart;
      var f = (this.format = new Tee.Format(chart));
      f.stroke.fill = "silver";
      f.stroke.cap = "butt";
      f.fill = "";
      this.visible = true;
      this.lineDash = false;
    }

    this.grid = new Grid(chart);

    /**
     * @constructor
     * @class Stroke parameters to draw small lines at axes labels positions
     * @param {Tee.Chart} chart The chart object this axis ticks object belongs to.
     * @property {Tee.Stroke} stroke Defines the visual attributes used to draw the axis ticks.
     * @property {Number} [length=4] The length of ticks in pixels.
     */
    function Ticks(chart) {
      this.chart = chart;
      this.stroke = new Stroke(chart);
      this.stroke.cap = "butt";
      this.visible = true;
      this.length = 4;
    }

    this.ticks = new Ticks(chart);

    this.innerTicks = new Ticks(chart);
    this.innerTicks.visible = false;

    var m = (this.minorTicks = new Ticks(chart));
    m.visible = false;
    m.length = 2;
    m.count = 3;

    /**
     * @constructor
     * @augments Tee.Annotation
     * @class Text and formatting properties to display near axes
     * @param {Tee.Chart} chart The chart object this axis title object belongs to.
     * @param {Boolean} [transparent=true] Determines if axis title will be displayed or not.
     */
    function Title(chart) {
      Tee.Annotation.call(this, chart);
      this.padding = 4;
      this.transparent = true;
      this.rotation = 0;
      this.format.font.textAlign = "center";

      this.drawIt = function (textAlign, x, y, rotation) {
        this.format.font.textAlign = textAlign;

        if (rotation === 0) {
          this.position.x = x;
          this.position.y = y;
          this.forceDraw();
        } else {
          var ctx = chart.ctx;
          ctx.save();

          ctx.translate(x, y);
          ctx.rotate((-rotation * Math.PI) / 180);

          this.position.x = 0;
          this.position.y = 0;

          this.forceDraw();
          ctx.restore();
        }
      };
    }

    Title.prototype = new Tee.Annotation();

    this.title = new Title(chart);
    if (!horizontal) this.title.rotation = otherSide ? 270 : 90;

    this.automatic = true;
    this.minimum = 0;
    this.maximum = 0;
    this.increment = 0;
    this.log = false;

    this.startPos = 0;
    this.endPos = 0;

    this.start = 0; // %
    this.end = 100; // %

    this.axisSize = 0;

    this.scale = 0;
    this.increm = 0;

    function calcWordWrap(f, s) {
      var r = 0,
        w,
        ss = s.split(" "),
        t;

      for (t = 0; t < ss.length; t++) {
        w = f.textWidth(ss[t]);
        if (w > r) r = w;
      }

      return r;
    }

    function toRadians(angle) {
      return angle * (Math.PI / 180);
    }

    /**
     * @returns {Number} Returns the approximated width in pixels of largest axis label.
     */
    this.minmaxLabelWidth = function (adjust) {
      var l = this.labels,
        f = l._text,
        w = 0,
        s,
        wordWrap;
      var la = this.labels,
        l;

      if ((f !== null) && (f !== undefined)){
        wordWrap = l.wordWrap == "auto" || l.wordWrap == "yes";

        la.format.font.prepare();

        for (var t = 0, le = f.data.labels.length; t < le; t++) {
          s = f.data.labels[t];
          if (l.ongetlabel) s = l.ongetlabel(t, s);

          if (s)
            w = Math.max(
              w,
              wordWrap ? calcWordWrap(l.format, s) : l.format.textWidth(s)
            );
        }
      } else {
        var mi = this.roundMin(),
          ma = this.maximum;
        w = Math.max(l.width(mi), l.width(0.5 * (mi + ma)));

        w = Math.max(w, l.width(0.0000001));
        w = Math.max(w, l.width(ma));
      }

      //adjust for rotation (if any). Are we adjusting or spacing?
      if (l.rotation == 0) {
        if ((this.horizontal && adjust) || (!this.horizontal && !adjust)) {
          w = la.format.textHeight("Wj");
        }
      } else if (this.horizontal) {
        if (adjust) {
          w = Math.abs(Math.sin(toRadians(l.rotation)) * w);
        } else {
          w = Math.abs(Math.cos(toRadians(l.rotation)) * w);
        }
      } else {
        if (adjust) {
          w = Math.abs(Math.cos(toRadians(l.rotation)) * w);
        } else {
          w = Math.abs(Math.sin(toRadians(l.rotation)) * w);
        }
      }

      //guarantee minimum spacing
      if (w < la.format.textHeight("Wj")) w = la.format.textHeight("Wj");
      return w;
    };

    this.checkRange = function () {
      if (this.maximum - this.minimum < this.minAxisRange)
        this.maximum = this.minimum + this.minAxisRange;
    };

    this.checkMinMax = function () {
      var s = this.chart.series,
        h = this.horizontal;

      if (this.automatic) {
        this.minimum = h ? s.minXValue(this) : s.minYValue(this);
        this.maximum = h ? s.maxXValue(this) : s.maxYValue(this);

        this.checkRange();
      }
    };

    /**
     * @returns {Number} Returns if any visible series has less than n values.
     * Only called from Axis calcIncrement, to avoid axis label increments to be
     * smaller than series number of points.
     */
    function anySeriesHasLessThan(c, n) {
      var t, s, isY;

      for (t = 0; (s = c.chart.series.items[t++]); ) {
        if (s.visible && s.sequential) {
          isY = s.yMandatory;

          if ((isY && c.horizontal) || (!isY && !c.horizontal)) {
            if (s.associatedToAxis(c)) {
              if (s.count() <= n) return true;
            }
          }
        }
      }

      return false;
    }

    /**
     * @returns {Number} Returns the next bigger value in the sequence 1,2,5,10,20,50...
     */
    function nextStep(value) {
      if (!isFinite(value)) return 1;
      else if (value >= 10) return 10 * nextStep(0.1 * value);
      else if (value < 1) return 0.1 * nextStep(value * 10);
      else return value < 2 ? 2 : value < 5 ? 5 : 10;
    }

    /**
     * @returns {Number} Returns the best appropriate distance between axis labels.
     */
    function calcIncrement(c, maxLabelSize) {
      if (c.maximum == c.minimum) return 1;
      else {
        var tmp = c.axisSize / maxLabelSize,
          less = anySeriesHasLessThan(c, tmp);
        tmp = Math.abs(c.maximum - c.minimum) / (tmp + 1);
        return less ? Math.max(1, tmp) : nextStep(tmp);
      }
    }

    this.calcAxisScale = function () {
      var range = this.maximum - this.minimum;
      if (range === 0) range = 1;
      else if (this.log) range = Math.log(range);

      this.scale = this.axisSize / range;
    };

    this.calcScale = function () {
      var la = this.labels,
        l;

      la.format.font.prepare();
      l = this.minmaxLabelWidth(false);

      if (la.alternate) l *= 0.5;

      l *= 1 + la.separation * 0.02;

      this.increm =
        this.increment === 0 ? calcIncrement(this, l) : this.increment;

      if (this.increm <= 0) this.increm = 0.1;
      else if (this.increm > 0 && this.increm <= 0.00000001)
        this.increm = 0.00000001;
    };

    /**
     * @returns {Boolean} Returns the first visible series associated to this axis, or null if any.
     */
    this.hasAnySeries = function () {
      var li = this.chart.series.items,
        t,
        s,
        d,
        h;

      for (t = 0; (s = li[t++]); ) {
        // DB fix Sep-2013, empty series should not be considered:
        if (
          s.visible &&
          s.associatedToAxis(this) &&
          (s.__alwaysDraw || s.count() > 0)
        ) {
          // Remember now if series "s" has date-time values:

          h = this.horizontal;
          if (s.yMandatory) h = !h;

          d = h ? s.data.values : s.data.x;
          this.dateTime = d && d.length > 0 && d[0] instanceof Date;

          return s;
        }
      }

      return null;
    };

    this.drawAxis = function () {
      var t = this,
        f = t.format,
        c = t.chart.ctx,
        pos = t.axisPos,
        start = t.startPos,
        end = t.endPos;

      var rad = 20 * f.depth,
        r;

      if (this.chart.aspect.view3d && rad > 0) {
        if (horizontal)
          r = { x: start, y: pos - rad * 0.5, width: end - start, height: rad };
        else
          r = { x: pos - rad * 0.5, y: start, width: rad, height: end - start };

        var old = this.z;
        f.z = old - f.depth * 0.5;
        f.cylinder(r, 1, !horizontal);
        f.draw(c, null, r);
        f.z = old;
      } else {
        c.z = this.z;

        c.beginPath();

        if (horizontal) {
          c.moveTo(start, pos);
          c.lineTo(end, pos);
        } else {
          c.moveTo(pos, start);
          c.lineTo(pos, end);
        }

        f.stroke.prepare();
        c.stroke();
      }
    };

    this.drawGrids = function () {
      var c = this.chart.ctx,
        p,
        r = this.chart.chartRect,
        f = this.grid.format,
        x1,
        y1,
        x2,
        y2,
        v,
        vmin = this.roundMin();

      if (this.grid.centered) {
        var tmp = this.increm * 0.5;
        if (vmin - tmp >= this.minimum) vmin -= tmp;
        else vmin += tmp;
      }

      var a = this.chart.aspect,
        is3d = a.view3d,
        isOrtho = is3d && a.orthogonal,
        off3d = is3d ? 1 : 0;

      c.beginPath();

      if (horizontal) {
        y1 = this.bounds.y - off3d;
        y2 = otherSide ? r.getBottom() - 1 : r.y + 1;
      } else {
        x1 = this.bounds.x + off3d;
        x2 = otherSide ? r.x + 1 : r.getRight() - 1;
      }

      var oldpos,
        pos = -1;

      if (f.fill !== "") {
        v = vmin;

        var old = f.stroke.fill;
        f.stroke.fill = "";

        while (v <= this.maximum) {
          p = this.calc(v);

          if (pos % 2 === 0)
            horizontal
              ? f.rectangle(oldpos, y2, p - oldpos, y1 - y2)
              : f.rectangle(x1, oldpos, x2 - x1, p - oldpos);

          oldpos = p;

          v += this.increm;
          pos++;
        }

        f.stroke.fill = old;
        c.fillStyle = "";
      }

      v = vmin;

      if (isOrtho)
        if (horizontal) {
          y1 -= a._orthoy;
          y2 -= a._orthoy;
        } else {
          x1 += a._orthox;
          x2 += a._orthox;
        }

      var isCustomDash =
        f.stroke.dash && !c.setLineDash && !c.mozCurrentTransform;

      c.z = is3d ? this.chart.walls.back.format.z - 0.01 : 0;

      while (v <= this.maximum) {
        pos = this.calc(v);

        horizontal ? (x1 = x2 = pos) : (y1 = y2 = pos);

        if (isOrtho) {
          if (horizontal) {
            x1 += a._orthox;
            x2 += a._orthox;
          } else {
            y1 -= a._orthoy;
            y2 -= a._orthoy;
          }
        }

        // TODO: lineZ (3D grid sides)

        if (is3d && !this.otherSide && c.lineZ) c.lineZ(x1, y1, 0, c.z);

        if (isCustomDash) dashedLine(c, x1, y1, x2, y2);
        else {
          c.moveTo(x1, y1);
          c.lineTo(x2, y2);
        }

        v += this.increm;
      }

      f.stroke.prepare();
      f.shadow.prepare(c);

      c.stroke();
    };

    function truncFloat(n) {
      return n - (n % 1);
    }

    /**
     * @returns {Number} Returns the axis minimum value rounded according the axis increment distance.
     */
    this.roundMin = function () {
      // OLD: var v=trunc(this.minimum/this.increm);

      // NEW: Fix against rounding precision of "trunc" (|0) operator:

      if (this.increm === 0 || !this.labels.roundFirst) return this.minimum;
      else {
        //var v = parseFloat(new Number(this.minimum/this.increm).toFixed(0));

        var v = truncFloat(this.minimum / this.increm);

        return this.increm * (this.minimum <= 0 ? v : 1 + v);
      }
    };

    this.drawTicks = function (t, factor, mult) {
      var v = this.roundMin(),
        tl = 1 + t.length,
        tl2 = 1;

      if ((horizontal && otherSide) || (!horizontal && !otherSide)) {
        tl = -tl;
        tl2 = -1;
      }

      tl *= factor;
      tl2 *= factor;

      tl += this.axisPos;
      tl2 += this.axisPos;

      var p,
        inc,
        n = 1,
        cou = 0;

      if (mult === 0) inc = this.increm;
      else {
        n += mult;
        inc = this.increm / n;
      }

      var c = this.chart.ctx;

      c.beginPath();

      while (v <= this.maximum) {
        if (mult === 0 || cou++ % n !== 0) {
          p = this.calc(v);

          if (horizontal) {
            c.moveTo(p, tl2);
            c.lineTo(p, tl);
          } else {
            c.moveTo(tl2, p);
            c.lineTo(tl, p);
          }
        }

        v += inc;
      }

      t.stroke.prepare();

      t.z = this.z;
      c.z = t.z;

      c.stroke();
    };

    this.drawTitle = function () {
      var l = this.labels,
        tmpX,
        tmpY,
        titleBounds = this.title.bounds,
        rotation = this.title.rotation,
        //ctx=this.chart.ctx,
        textAlign = "center";

      if (this.title.text !== "") {
        if (horizontal) {
          tmpY = this.title.padding;

          if (this.ticks.visible) tmpY += this.ticks.length;

          if (l.visible) {
            l.format.font.prepare();
            var h = this.maxLabelDepth;
            if (l.alternate) h *= 2;
            tmpY += h;
          }

          tmpX = this.startPos + this.axisSize * 0.5;

          if (this.otherSide) tmpY = -tmpY - titleBounds.height;

          tmpY = this.axisPos + tmpY;

          if (rotation === 0) {
            tmpX -= titleBounds.width * 0.5;
          } else {
            tmpX += titleBounds.height * (this.otherSide ? -0.5 : 0.5);
            if (!this.otherSide) tmpY += 1.5 * titleBounds.height;
            textAlign = this.otherSide
              ? "near"
              : rotation === 270
              ? "near"
              : "far";
          }
        } else {
          tmpX = this.title.padding;

          if (this.ticks.visible) tmpX += this.ticks.length;

          if (l.visible) {
            var w = l.maxWidth;
            if (l.alternate) w *= 2;
            tmpX += w;
          }

          tmpY = this.startPos + 0.5 * this.axisSize;

          if (rotation === 0) {
            tmpX = this.axisPos + (this.otherSide ? tmpX : -tmpX);
            tmpY -= 0.5 * titleBounds.height;
            textAlign = this.otherSide ? "near" : "far";
          } else {
            tmpX += titleBounds.height;
            tmpX = this.axisPos + (this.otherSide ? tmpX : -tmpX);
            tmpY += titleBounds.width * (this.otherSide ? -0.5 : 0.5);
          }
        }

        this.title.drawIt(textAlign, tmpX, tmpY, rotation);

        //      chart.ctx.rect(tmpX,tmpY,titleBounds.width,titleBounds.height);
        //      chart.ctx.stroke();
      }
    };

    this.rotatedWidth = function (l, w) {
      return Math.abs(Math.sin(toRadians(l.rotation)) * w);
    };

    this.drawLabel = function (value, r) {
      var l = this.labels,
        s = l.getLabel(value);

      /*
    if (this.firstSeries && (this.firstSeries.notmandatory==this))
       s=l.getLabel(this.firstSeries,value);
    else
       s=l.getLabel(null,value);
    */

      r.width = l.format.textWidth(s);
      if (r.width > l.maxWidth) l.maxWidth = r.width;

      if (l.rotation == 0)
        this.horizontal ? (r.x -= 0.5 * r.width) : (r.y += r.height * 0.5);
      else {
        var c = this.chart.ctx;
        c.save();
        c.translate(r.x, r.y);
        c.rotate((-Math.PI * l.rotation) / 180);
        c.textAlign = "right";

        if (this.horizontal) {
		  var xDisplacement = this.rotatedWidth(l, r.width) * 0.5;
		  var yDisplacement = (this.rotatedWidth(l, r.height) * 0.5) - 4;
		  if (l.rotation >= 220)
			 (this.otherSide) ? r.x = xDisplacement * -1 : r.x = xDisplacement;
		  else
			  (this.otherSide) ? r.x = xDisplacement +4 : r.x = xDisplacement  * -1;

          (this.otherSide) ?  r.y = yDisplacement : r.y = (yDisplacement * -1);
        } else {
			
		  var xDisplacement = this.rotatedWidth(l, r.width);
		  //var yDisplacement = (this.rotatedWidth(l, r.height));	 
		  
		  (this.otherSide) ? r.x = xDisplacement : r.x = 30;
          r.y = -8;
        }
      }

      if (l.format.font.textAlign == "right") r.x -= r.width;

      l.format.z = this.z * this.chart.walls.back.format.z;

      l.format.drawText(r, s);

      if (l.rotation !== 0) this.chart.ctx.restore();
    };

    this.drawLabels = function () {
      var v = this.roundMin(),
        r = new Rectangle(),
        c = this.axisPos,
        l = this.labels;
      //var v=(this.minimum<this.maximum) ? this.roundMin() : this.minimum, r=new Rectangle(), c=this.axisPos, l=this.labels;

      l.maxWidth = 0;
      l.format.font.prepare();

      var tl = this.ticks.visible ? this.ticks.length : 0;

      tl += l.padding;

      if (this.horizontal) this.otherSide ? (c -= tl) : (c += tl);
      else this.otherSide ? (c += tl) : (c -= tl);

      var oldc = c;

      r.height = l.format.textHeight("Wj");

      var alter = l.alternate,
        w = alter
          ? this.horizontal
            ? -this.minmaxLabelWidth(true)
            : this.minmaxLabelWidth(true)
          : 0,
        p;

      // TODO: if ongetlabels .... (loop for user-custom labels position and text)

      while (v <= this.maximum) {
        p = this.calc(v);

        if (this.horizontal) {
          r.x = p;
          r.y = c; // -r.height*0.5;
        } else {
          r.x = c;
          r.y = p - r.height * 0.5;
        }

        if (alter)
          c = c == oldc ? (this.otherSide ? oldc + w : oldc - w) : oldc;

        this.drawLabel(v, r);
        v += this.increm;
      }
    };

    /**
     * @returns {Number} Returns the position in pixels for a given value, using the axis scales.
     */
    this.calc = function (value) {
      var p;

      if (value !== value)
        // isNaN() <-- slow
        p = 0;
      else if (this.log) {
        value -= this.minimum;
        p = value <= 0 ? 0 : Math.log(value) * this.scale;
      } else p = (value - this.minimum) * this.scale;

      if (this.horizontal)
        return this.inverted ? this.endPos - p : this.startPos + p;
      else return this.inverted ? this.startPos + p : this.endPos - p;
    };

    /**
     * @returns {Number} Returns the axis value for a given position in pixels.
     */
    this.fromPos = function (p) {
      var i = this.horizontal;
      if (this.inverted) i = !i;
      return (
        this.minimum + (i ? p - this.startPos : this.endPos - p) / this.scale
      );
    };

    this.fromSize = function (p) {
      return p / this.scale;
    };
    /**
     * @returns {Number} Returns the index of a given position in pixels (only works in bottom axes).
     */
    this.fromSizeCalcIndex = function (p) {
      var index = -1;
      if (this.dateTime == true) {
        var i = 0,
          inc = 0,
          actualValue =
            ((this.maximum - this.minimum) / this.axisSize) * p + this.minimum;

        if (this.chart.series.items[0].data.x.length > 1) {
          inc =
            this.chart.series.items[0].data.x[1].getTime() -
            this.chart.series.items[0].data.x[0].getTime();
        }
        while (index == -1 && i < this.chart.series.items[0].data.x.length) {
          if (
            this.chart.series.items[0].data.x[i].getTime() + inc / 2 >
              actualValue &&
            actualValue + inc / 2 >
              this.chart.series.items[0].data.x[i].getTime()
          ) {
            index = i;
          } else i++;
        }
      } else {
        index = p / this.scale + this.minimum;
        if (
          this.chart.series.items[0].data.values.length < index ||
          index < -0.5
        )
          index = -1;
      }
      return index;
    };
    /**
     * @returns {Number} Returns the size in pixels of a given value, using the axis scales.
     */
    this.calcSize = function (value) {
      return Math.abs(this.calc(value) - this.calc(0));
    };

    /**
     * @param {Number} p1 Position in pixels to be axis minimum.
     * @param {Number} p2 Position in pixels to be axis maximum.
     */
    this.calcMinMax = function (p1, p2) {
      this.automatic = false;

      var a = this.fromPos(p1),
        b = this.fromPos(p2),
        tmp;
      if (a > b) {
        tmp = a;
        a = b;
        b = tmp;
      }

      this.minimum = a;
      this.maximum = b;

      this.checkRange();
    };

    this.minAxisRange = 0.0000000001;

    this.setMinMax = function (min, max) {
      this.automatic = false;

      this.minimum = min;
      this.maximum = max;

      this.checkRange();
    };
  }

  Axis.adjustRect = function () {
    var s = 0,
      l = this.labels,
      b = this.chart.chartRect,
      ti = this.title,
      hasTitle;

    // Recalc firstSeries even if this axis is not visible:
    this.firstSeries = this.hasAnySeries();

    if (!this.visible) return;

    if (this.firstSeries && this.visible) {
      this.checkMinMax();

      l.checkStyle();

      hasTitle = ti.shouldDraw();
      if (hasTitle) ti.resize();

      if (b.automatic && !this.custom) {
        if (l.visible) {
          l.format.font.prepare();
          s = this.minmaxLabelWidth(true);
          this.maxLabelDepth = s;

          if (l.alternate) s *= 2;
          s += l.padding;
        }

        if (this.ticks.visible) s += this.ticks.length;

        if (hasTitle) s += ti.bounds.height; //+ti.padding;

        if (this.horizontal)
          this.otherSide ? b.setTop(b.y + s) : (b.height -= s);
        else this.otherSide ? (b.width -= s) : b.setLeft(b.x + s);
      }
    }
  };

  Axis.prototype.setPos = function (a, b) {
    this.startPos = a + this.start * b * 0.01;
    this.endPos = a + this.end * b * 0.01;
    this.axisSize = this.endPos - this.startPos;
  };

  /**
   * Axis rect calcs. Returns current ChartRect.
   */
  Axis.calcRect = function () {
    if (!this.firstSeries) return;

    this.checkMinMax();

    // Calc bounds
    var b = this.chart.chartRect,
      bo = this.bounds,
      h = this.horizontal;

    if (h) {
      bo.y = this.otherSide ? b.y : b.y + b.height;
      bo.width = b.width;
      this.setPos(b.x, b.width);
    } else {
      bo.x = this.otherSide ? b.x + b.width : b.x;
      bo.height = b.height;
      this.setPos(b.y, b.height);
    }

    this.calcAxisScale();

    var tmp = this.chart.series;

    //specific case: check for non-std sideAll scaling
    if (tmp.items.length > 0) {
      if (this.automatic) {
        var s = tmp.items[0];

        if (
          s instanceof Tee.Bar &&
          s.notmandatory == this &&
          s.stacked == "sideAll"
        ) {
          s.notmandatory.minimum = -0.5;
          s.notmandatory.maximum = s.countAll(false) - 0.5;
        }
      }
    }

    // Calculate axis margins:
    if (this.automatic) {
      var s = this.chart.series,
        m = h ? s.horizMargins() : s.vertMargins(),
        hasX = m.x > 0,
        hasY = m.y > 0;

      if (hasX) this.minimum -= this.fromSize(m.x);

      if (hasY) this.maximum += this.fromSize(m.y);

      if (hasX || hasY) this.calcAxisScale();
    }

    this.calcScale();

    // Calc pos

    var v = (h ? b.height : b.width) * this.position * 0.01,
      w = this.chart.walls,
      wallsize = 0,
      haswalls = w.visible && this.chart.aspect.view3d;

    if (h) {
      if (haswalls && w.bottom.visible) wallsize = w.bottom.size;
      this.axisPos = this.otherSide ? b.y + v : b.getBottom() + wallsize - v;
    } else {
      if (haswalls && w.left.visible) wallsize = w.left.size;
      this.axisPos = this.otherSide ? b.getRight() - v : b.x - wallsize + v;
    }
  };

  Axis.draw = function () {
    if (this.visible && this.firstSeries) {
      this.z = this.otherSide ? this.chart.walls.back.format.z : 0;

      //guarantee centre label for one-value axes.
      if (
        Math.abs(this.maximum - this.minimum) <=
        0.000000001 /*widen beyond this.minAxisRange*/
      ) {
        var diff =
          Math.abs(this.minimum) < 0.000000001
            ? 0.00000001
            : this.minimum / 10000000;

        if (
          this.minimum != this.minimum - diff ||
          this.maximum != this.minimum + diff
        ) {
          this.setMinMax(this.minimum - diff, this.maximum + diff);
          if (this.chart != null) this.chart.draw();
        }
      }

      if (this.format.stroke.fill !== "") this.drawAxis();

      // Protect against infinite loop:
      if (this.roundMin() + 1000 * this.increm > this.maximum) {
        if (this.grid.visible) this.drawGrids();
        if (this.ticks.visible) this.drawTicks(this.ticks, 1, 0);
        if (this.innerTicks.visible) this.drawTicks(this.innerTicks, -1, 0);
        if (this.minorTicks.visible)
          this.drawTicks(
            this.minorTicks,
            1,
            Math.max(0, this.minorTicks.count)
          );
        if (this.labels.visible) this.drawLabels();
      }

      if (this.title.shouldDraw()) this.drawTitle();
    }
  };

  /**
   * @memberOf Tee.Chart
   * @constructor
   * @class Displays a list of chart series data
   * @param {Tee.Chart} chart The parent chart this legend object belongs to.
   * @property {Number} align Legend position as offset % of chart size. Default 0.
   * @property {Number} padding Percent of chart size pixels to leave as margin from legend.
   * @property {Boolean} transparent Determines to draw or not the legend background.
   * @property {Tee.Format} format Formatting properties to draw legend background and items.
   * @property {Tee.Format} hover Limited Formatting properties for mouseover hover of text. Offers color and enabled.
   * @property {Tee.Annotation} title Draws a title on top of legend.
   * @property {Rectangle} bounds Defines the legend position in pixels.
   * @property {String} position Automatic position legend ("left", "top", "right" or "bottom").
   * @property {Boolean} visible Defines to draw or not the legend.
   * @property {Boolean} inverted When true, legend items are displayed in inverted order.
   * @property {Boolean} fontColor Determines to fill each legend item text using series or point colors.
   * @property {Stroke} dividing Draws a line between legend items.
   * @property {Symbol} symbol Properties to draw a small indicator next to each legend item.
   * @property {String} legendStyle Determines to draw all visible series names or first visible series points. ("auto", "series", "values").
   * @property {String} textStyle What to draw at each legend item for series values: "auto", "valuelabel", "label", "value", "percent", "index", "labelvalue", "percentlabel", "valuepercent"
   */
  function Legend(chart) {
    this.chart = chart;

    this.transparent = false;

    this.useStrokeColor = false;

    var f = (this.format = new Tee.Format(chart));
    f.fill = "white";
    f.round.x = 8;
    f.round.y = 8;
    f.font.baseLine = "top";
    f.shadow.visible = true;

    f.z = 0;
    f.depth = 0.05;

    this.title = new Tee.Annotation(chart);
    this.title.transparent = true;

    this.bounds = new Rectangle();
    this.position = "right"; // left, top, right, bottom, custom
    this.visible = true;
    this.inverted = false;

    this.padding = 5; // margin from legend to axes edge, percent of legend size

    this.margin = 5; // margin from legend to chart edge, percent of legend size

    this.align = 0; // % default = 0

    this.fontColor = false;

    var d = (this.dividing = new Stroke(chart));
    d.fill = ""; //"rgb(220,220,220)";
    d.cap = "butt";

    this.over = -1;

    /**
     * @constructor
     * @public
     * @class Displays a symbol at chart legend for each legend item
     * @param {Tee.Chart} chart The parent chart this legend symbol object belongs to.
     * @property {String} string Draws a rectangle shape or a line as a symbol.
     */
    function Symbol(chart) {
      this.chart = chart;

      var f = (this.format = new Tee.Format(chart)),
        s = f.shadow;

      s.visible = true;
      s.color = "silver";
      s.width = 2;
      s.height = 2;

      f.depth = 0.01;

      this.width = 8;
      this.height = 8;
      this.padding = 8; // "100%"

      this.style = "rectangle"; // "line"
      this.visible = true;

      function tryHover(series, index) {
        if (series.hover.enabled) {
          var sv = chart.legend.showValues();

          if ((sv && series.over == index) || (!sv && series.over >= 0))
            return series.hover;
        }

        return null;
      }

      this.draw = function (series, index, x, y) {
        var c = chart.ctx,
          fhover = tryHover(series, index),
          old = f.fill,
          olds = f.stroke;

        f.fill = series.legendColor(index);

        c.z = -0.01;

        switch (this.style) {
          case "rectangle":
            if (fhover) f.stroke = fhover.stroke; // ??

            if (this.chart.aspect.view3d) {
              var r = {
                x: x,
                y: y - this.height * 0.5 - 1,
                width: this.width,
                height: this.height,
              };

              old = f.z;

              var oldz = c.z;

              f.z = c.z - f.depth;

              f.cube(r);
              f.draw(c, null, r);

              f.z = old;
              c.z = oldz;
            } else
              f.rectangle(
                x,
                y - this.height * 0.5 - 1,
                this.width,
                this.height
              );
            break;
          case "triangle":
            if (fhover) f.stroke = fhover.stroke;

            f.polygon([
              new Point(x + this.width * 0.5, y - this.height * 0.5),
              new Point(x + this.width, y + this.height * 0.5),
              new Point(x, y + this.height * 0.5),
            ]);

            break;
          case "ellipse":
            if (fhover) f.stroke = fhover.stroke;

            f.ellipse(x + this.width * 0.5, y, this.width, this.height);
            break;
          default:
            //"line"
            c.beginPath();
            c.moveTo(x, y);
            c.lineTo(x + this.width, y);

            if (fhover) fhover.stroke.prepare();
            else f.stroke.prepare(f.fill);

            c.stroke();
            break;
        }

        f.fill = old;
        f.stroke = olds;
      };
    }

    /*
     * Contains properties to paint a visual representation near each legend item.
     */
    this.symbol = new Symbol(chart);

    this.itemHeight = 10;
    this.innerOff = 0;

    this.legendStyle = "auto"; // auto, series, values, ...
    this.textStyle = "auto"; // valuelabel, label, value, percent, index, labelvalue, percentlabel

    this.hover = new Tee.Format(chart);
    this.hover.enabled = true;
    this.hover.font.fill = "red";

    /**
     * @returns {Number} Returns the width in pixels of legend items, including text and symbols if visible.
     */
    this.totalWidth = function () {
      var w = itemWidth + 8,
        s = this.symbol;
      if (s.visible) w += s.width + s.padding;
      return w;
    };

    var titleHeight = 0;

    this._space = function () {
      return (
        this.bounds.y + titleHeight + this.margin * chart.bounds.height * 0.01
      );
    };

    /**
     * @returns {Number} Returns the maximum number of vertical rows using the available height.
     */
    this.availRows = function () {
      var h = this._space() + this.itemHeight * 0.5;
      if (!h) h = 0;
      return trunc((chart.bounds.getBottom() - h) / this.itemHeight);
    };

    function seriesCount(legend) {
      var ss = chart.series;
      return legend.showHidden ? ss.items.length : ss.visibleCount();
    }

    /**
     * @returns {Number} Returns the number of legend items that should be displayed.
     */
    this.itemsCount = function () {
      var ss = chart.series,
        result = seriesCount(this),
        rr = chart.bounds;

      if (result === 0) return 0;

      var st = this.legendStyle;

      if (st === "values" && result > 0)
        result = ss.firstVisible().legendCount();
      else {
        if ((st === "auto" && result > 1) || st === "series") {
          var t;
          for (t = 0; t < ss.items.length; t++)
            if (!ss.items[t].legend.visible) result--;
        } else if (result == 1) result = ss.firstVisible().legendCount();
      }

      if (this.isVertical()) {
        if ((0.5 + result) * this.itemHeight > rr.height - this._space())
          result = this.availRows();
      } else {
        this.rows = 1;

        var total = this.totalWidth(),
          pad = this.calcPadding(rr);

        var chartW = rr.width - 2 * pad;

        if (result * total > chartW) {
          var w = rr.x + pad;
          if (!w) w = 0;

          this.perRow = trunc(chartW / total);

          if (result > this.perRow) {
            this.rows = 1 + trunc(result / this.perRow);

            if (this.rows * this.itemHeight > rr.height - this.bounds.y) {
              this.rows = this.availRows();
              result = this.rows * this.perRow;
            }
          }
        } else this.perRow = result;
      }

      return result;
    };

    /**
     * @returns {Boolean} Returns if legend shows series titles or a series values.
     */
    this.showValues = function () {
      var s = this.legendStyle;
      return s == "values" || (s == "auto" && seriesCount(this) == 1);
    };

    /**
     * @returns {String} Returns the index'th legend text string.
     */
    this.itemText = function (series, index) {
      var res = series.legendText(index, this.textStyle, false, true);
      return this.ongettext ? this.ongettext(this, series, index, res) : res;
    };

    this.calcItemPos = function (index, pos) {
      var i = this.itemHeight,
        b = this.bounds;
      pos.x = b.x;
      pos.y = b.y + this.innerOff;

      if (this.isVertical()) {
        pos.x += b.width - 6 - this.innerOff;
        pos.y += i * 0.4 + index * i + titleHeight;
      } else {
        pos.x +=
          this.innerOff + (1 + (index % this.perRow)) * this.totalWidth();
        pos.y += i * (trunc(index / this.perRow) + 0.25);
      }
    };

    this.calcItemRect = function (index, r) {
      var i = this.itemHeight,
        b = this.bounds;
      r.height = i;
      r.x = b.x;
      r.y = b.y;

      if (this.isVertical()) {
        r.width = b.width;
        r.y += i * 0.4 + index * i + titleHeight;
      } else {
        r.width = this.totalWidth();
        r.x += this.innerOff + (index % this.perRow) * r.width;
        r.y += i * (trunc(index / this.perRow) + 0.25);
      }
    };

    var rMouse = new Rectangle();

    this.mousedown = function (p) {
      if (this.onclick && this.over !== -1) {
        if (this.showValues())
          this.onclick(chart.series.firstVisible(), this.over);
        else {
          var ss = chart.series.items,
            l = ss.length,
            c = 0;
          for (var t = 0; t < l; t++)
            if (this.showHidden || ss[t].visible) {
              if (c == this.over) {
                this.onclick(ss[t], -1);
                break;
              } else c++;
            }
        }

        return true;
      }

      return false;
    };

    this.mousemove = function (p) {
      var n = this.over;

      if (this.bounds.contains(p)) {
        var c = this.itemsCount();
        for (var t = 0; t < c; t++) {
          this.calcItemRect(t, rMouse);
          if (rMouse.contains(p)) {
            n = t;
            break;
          }
        }
      } else n = -1;

      if (n != this.over) {
        if (this.onhover) this.onhover(this.over, n);

        this.over = n;

        var o = this.chart;
        window.requestAnimFrame(function () {
          o.draw();
        });
      }

      if (this.onclick) this.chart.newCursor = n === -1 ? "default" : "pointer";
    };

    this.drawSymbol = function (series, index, itemPos) {
      var s = this.symbol;

      s.draw(
        series,
        index,
        itemPos.x - itemWidth - s.width - s.padding,
        itemPos.y + this.itemHeight * 0.4
      );
    };

    var itemPos = { x: 0, y: 0 },
      r = new Rectangle(),
      aligns;

    this.drawItem = function (text, series, index, isSeries) {
      var stopLegend = false;

      var vertical = this.isVertical();

      this.calcItemPos(index, itemPos);

      r.x = itemPos.x;
      r.y = itemPos.y;

      if (vertical) r.y -= this.chart.isMozilla ? 0 : 2;
      else if (this.chart.isMozilla) r.y++;
      else r.y--;

      var old = f.font.fill;

      if (this.over == index)
        f.font.fill = this.hover.enabled ? this.hover.font.fill : old;
      else if (this.fontColor)
        f.font.fill = this.showValues()
          ? series.legendColor(index)
          : series.format.fill;
      else if (!series.visible) f.font.fill = "silver";

      var c = this.chart.ctx;

      if (isSeries) {
        f.font.textAlign = "start";
        r.x -= itemWidths[0];
        c.textAlign = f.font.textAlign;

        var symbolWidth = this.symbol.width;

        var getLegendArgs = { text, index, r, symbolWidth, stopLegend: false };

        if (this.onbeforedrawLegendItem)
          this.onbeforedrawLegendItem(getLegendArgs);

        if (getLegendArgs.stopLegend) stopLegend = true;

        text = getLegendArgs.text;

        if (!stopLegend) f.drawText(r, text);
      } else {
        if (!(text instanceof Array)) text = [text];

        if (vertical) {
          var l = text.length,
            oldW = r.width,
            sep = f.textWidth(" ");
          while (l--) {
            f.font.textAlign = aligns[l] ? "start" : "end";
            r.x -= itemWidths[l];

            c.textAlign = f.font.textAlign;

            var symbolWidth = this.symbol.width;

            var getLegendArgs = {
              text,
              index,
              r,
              symbolWidth,
              stopLegend: false,
            };

            if (this.onbeforedrawLegendItem)
              this.onbeforedrawLegendItem(getLegendArgs);

            if (getLegendArgs.stopLegend) stopLegend = true;

            text = getLegendArgs.text;

            if (!stopLegend) f.drawText(r, text[l]);

            r.width -= itemWidths[l] + sep;
          }
          r.width = oldW;
        } else {
          f.font.textAlign = "start";
          r.x -= itemWidth;
          c.textAlign = f.font.textAlign;

          var symbolWidth = this.symbol.width;

          var getLegendArgs = {
            text,
            index,
            r,
            symbolWidth,
            stopLegend: false,
          };

          if (this.onbeforedrawLegendItem)
            this.onbeforedrawLegendItem(getLegendArgs);

          if (getLegendArgs.stopLegend) stopLegend = true;

          text = getLegendArgs.text;

          if (!stopLegend) f.drawText(r, text.join(" "));
        }
      }

      if (!stopLegend) {
        f.font.fill = old;

        var hassymbol = !series.isColorEach || this.showValues();

        if (this.symbol.visible && hassymbol)
          this.drawSymbol(series, index, itemPos);

        if (index > 0 && this.dividing.fill !== "") {
          var b = this.bounds;
          c.beginPath();

          if (this.isVertical()) {
            c.moveTo(b.x, r.y - 2);
            c.lineTo(b.getRight(), r.y - 2);
          } else {
            var xx = r.x - itemWidth - 4,
              sy = this.symbol;
            if (sy.visible) xx -= sy.width + sy.padding;
            c.moveTo(xx, b.y);
            c.lineTo(xx, b.getBottom());
          }

          this.dividing.prepare();
          c.stroke();
        }
      }
    };

    /**
     * @returns {Boolean} Returns if index'th series is visible and has been displayed at legend.
     */
    this.drawSeries = function (index, order) {
      var s = chart.series.items[index];
      if ((this.showHidden || s.visible) && s.legend.visible) {
        this.drawItem(s.titleText(index), s, order, true);
        return true;
      } else return false;
    };

    this.draw = function () {
      var c = this.itemsCount(),
        len,
        t,
        ti = this.title,
        ctx = chart.ctx,
        old,
        ft = this.format.transparency,
        vertical = this.isVertical();

      if (c > 0) {
        var groups = ctx.beginParent;
        this.visual = groups ? ctx.beginParent() : null;

        f.cube(this.bounds); //avoids first Legend item being dropped from format in WebGL transparent Legends

        if (!this.transparent) {
          f.draw(ctx, null, this.bounds);
        }

        if (ft > 0) {
          old = ctx.globalAlpha;
          ctx.globalAlpha = (1 - ft) * old;
        }

        if (vertical && titleHeight > 0) {
          ti.bounds.x = this.bounds.x - 4;
          ti.bounds.y = this.bounds.y;
          ti.doDraw();
        }

        f.font.prepare();

        r.width = itemWidth;
        r.height = this.itemHeight;

        if (this.showValues()) {
          var s = chart.series.firstVisible(),
            order = 0;
          len = c;

          switch (this.textStyle) {
            case "auto":
            case "percentlabel":
            case "valuelabel":
              aligns = [false, true];
              break;
            case "labelpercent":
            case "labelvalue":
              aligns = [true, false];
              break;
            case "label":
              aligns = [true];
              break;
            case "valuepercent":
              aligns = [true, false];
              break;
            default:
              aligns = [false];
              break;
          }

          if (this.inverted)
            while (len--) this.drawItem(this.itemText(s, len), s, order++);
          else
            for (t = 0; t < len; t++) this.drawItem(this.itemText(s, t), s, t);
        } else {
          len = chart.series.count();
          if (vertical) len = Math.min(len, this.availRows());
          c = 0;

          aligns = [true];

          if (this.inverted)
            while (len--) {
              if (this.drawSeries(len, c)) c++;
            }
          else for (t = 0; t < len; t++) if (this.drawSeries(t, c)) c++;
        }

        if (ft > 0) ctx.globalAlpha = old;

        if (groups) ctx.endParent();
      }
    };

    var itemWidth, itemWidths;

    /**
     * @returns {Number} Returns the maximum width in pixels of all legend items text.
     */
    this.calcWidths = function () {
      var s,
        t,
        d,
        c,
        l = chart.series,
        text;

      itemWidth = 0;
      itemWidths = [0, 0];

      if (this.showValues()) {
        s = l.firstVisible();
        c = this.itemsCount();

        for (t = 0; t < c; t++) {
          text = this.itemText(s, t);
          if (!(text instanceof Array)) text = [text];

          if (text.length > 0) {
            d = f.textWidth(text[0]);
            if (d > itemWidths[0]) itemWidths[0] = d;

            if (text.length > 1) {
              d = f.textWidth(text[1]);
              if (d > itemWidths[1]) itemWidths[1] = d;
            }
          }
        }
      } else {
        c = l.count();

        for (t = 0; t < c; t++) {
          s = l.items[t];

          if (this.showHidden || s.visible) {
            d = f.textWidth(s.titleText(t));
            if (d > itemWidths[0]) itemWidths[0] = d;
          }
        }
      }

      itemWidth = itemWidths[0] + itemWidths[1];
    };

    /**
     * @returns {Number} Returns the distance in pixels between legend and chart bounds.
     */
    this.calcPadding = function (r) {
      // var p=this.padding, n = parseFloat( (p.indexOf("%") == -1) ? p : p.substring(0,p.length-1));

      return 0.01 * this.padding * (this.isVertical() ? r.width : r.height);
    };

    /**
     * @returns {Boolean} Returns if legend orientation is vertical.
     */
    this.isVertical = function () {
      var p = this.position;
      return p === "right" || p === "left";
    };

    this.calcrect = function () {
      var titleWidth = 0,
        t = this.title,
        r = chart.chartRect,
        a = this.align,
        b = this.bounds,
        vert = this.isVertical();

      if (t.shouldDraw()) {
        t.resize();
        titleHeight = t.bounds.height;
        titleWidth = t.bounds.width;
      } else titleHeight = 0;

      if (vert) b.y = a === 0 ? r.y : chart.bounds.height * a * 0.01;
      else b.x = a === 0 ? r.x : chart.bounds.width * a * 0.01;

      f.font.prepare();
      this.itemHeight = f.textHeight("Wj");

      this.calcWidths();

      var pad = this.calcPadding(r),
        s,
        co = this.itemsCount();

      if (vert) {
        s = this.symbol.visible ? this.symbol.width + this.symbol.padding : 0;

        b.width = Math.max(titleWidth, 12 + itemWidth + s);

        b.height = (0.5 + co) * this.itemHeight + titleHeight;

        if (b.width - 6 > titleWidth) t.bounds.width = b.width - 6;
      } else {
        b.width = pad + this.perRow * this.totalWidth();
        b.x += 0.5 * (r.width - b.width);
        b.height = this.itemHeight * (this.rows + 0.25);
      }

      if (f.stroke.fill !== "") {
        s = +f.stroke.size;
        if (s > 1) {
          b.width += s;
          b.height += s;
          this.innerOff = s * 0.5;
        }
      }

      // Resize chartRect:

      if (co === 0) return;

      if (this.position === "right") {
        b.x = r.getRight() - b.width - this.margin * b.width * 0.01;
        if (r.automatic) r.setRight(Math.max(r.x, b.x - pad));
      } else if (this.position === "left") {
        b.x = r.x;
        if (r.automatic) r.setLeft(b.x + b.width + pad);
      } else if (this.position === "top") {
        b.y = r.y + pad;
        if (r.automatic) r.setTop(b.getBottom() + pad);
      } else {
        b.y = r.getBottom() - b.height - pad;
        if (r.automatic) r.setBottom(b.y - pad);
      }
    };
  }

  /**
   * @memberOf Tee.Series
   * @constructor
   * @augments Tee.Annotation
   * @class Formatting properties to display annotations near series data points
   * @param {Tee.Series} series The parent series this marks object belongs to.
   * @param {Tee.Chart} chart The parent chart this marks object belongs to.
   * @property {Tee.Format} arrow Displays a line from mark to corresponding series point.
   * @property {Number} [arrow.length=10] Distance in pixels from mark to corresponding series point.
   * @property {Boolean} [arrow.underline=false] Draws a line under mark text.
   * @property {String} [style="auto"] Determines the text to display inside mark.
   * @property {Boolean} visible Defines if series marks are to be displayed or not.
   * @property {Number} [drawEvery=1] Controls how many marks to skip in between. (Useful for large series).
   */
  function Marks(series, chart) {
    Tee.Annotation.call(this, chart);
    this.series = series;

    var arrow = (this.arrow = new Tee.Format(chart));
    arrow.visible = true;
    arrow.length = 10;
    arrow.underline = false;
    arrow.z = 0.5;
    arrow.depth = 0.1;

    this.style = "auto"; // "value", "percent", "label", "valuelabel", "percentlabel" ...

    this.drawEvery = 1;
    this.visible = false;
    this.format.z = 0.5;

    /*
     * @private
     */
    this.setChart = function (chart) {
      this.chart = chart;
      this.format.setChart(chart);
      arrow.setChart(chart);
    };

    this.drawPolar = function (center, radius, angle, index) {
      var text = this.series.markText(index),
        px = center.x + Math.cos(angle) * radius,
        py = center.y + Math.sin(angle) * radius,
        c = this.chart.ctx;

      this.text = text;
      this.resize();

      var b = this.bounds,
        p2x,
        p2y,
        p = this.position;

      radius += arrow.length;
      (p2x = center.x + Math.cos(angle) * radius),
        (p2y = center.y + Math.sin(angle) * radius);

      if (p2x - b.width < 0) p2x -= p2x - b.width - 4;

      if (Math.abs(p2x - center.x) < b.width) p.x = p2x - b.width * 0.5;
      else p.x = p2x < center.x ? p2x - b.width : p2x;

      if (Math.abs(p2y - center.y) < b.height) p.y = p2y - b.height * 0.5;
      else p.y = p2y < center.y ? p2y - b.height : p2y;

      c.beginPath();
      c.moveTo(px, py);
      c.lineTo(p2x, p2y);

      if (arrow.underline) {
        if (p2y <= p.y || p2y >= p.y + b.height) {
          c.moveTo(p.x, p2y);
          c.lineTo(p.x + b.width, p2y);
        }
      }

      arrow.stroke.prepare();
      if (arrow.visible) c.stroke();

      this.draw();
    };

    this.canDraw = function (x, y, index, inverted) {
      var s = this.series.markText(index);

      if (s && s !== "" && (this.showZero || s !== "0")) {
        this.text = s;
        this.resize();

        var factor = inverted ? -1 : 1,
          r = this.bounds,
          m = this.series.yMandatory;

        if (m) {
          r.x = x - r.width * 0.5;
          r.y = y - factor * (arrow.length + (inverted ? 0 : r.height));
        } else {
          r.x = x + factor * arrow.length;
          if (inverted) r.x -= r.width;
          r.y = y - r.height * 0.5;
        }

        this.position.x = r.x;
        this.position.y = r.y;

        return true;
      } else return false;
    };

    this.drawMark = function (x, y, index, inverted) {
      if (this.canDraw(x, y, index, inverted)) {
        this.draw();

        if (arrow.visible) {
          var r = this.bounds,
            m = this.series.yMandatory;

          var rbot = inverted ? r.y : r.getBottom(),
            c = this.chart.ctx,
            is3d = this.chart.aspect.view3d;

          if (m) {
            if (is3d) {
              var rr = { x: x - 3, y: rbot, width: 6, height: y - rbot };

              arrow.z = this.format.z - arrow.depth * 0.5;

              arrow.cylinder(rr, 1, true);
              arrow.draw(this.chart.ctx, null, rr);

              return;
            } else {
              c.beginPath();

              c.moveTo(x, rbot);
              c.lineTo(x, y);

              if (arrow.underline) {
                c.moveTo(r.x, rbot);
                c.lineTo(r.x + r.width, rbot);
              }
            }
          } else {
            var py = r.y + r.height * 0.5;

            c.beginPath();
            c.moveTo(x, py);

            if (inverted) r.x += r.width;
            c.lineTo(r.x, py);

            if (arrow.underline) {
              c.moveTo(r.x, r.y + r.height);
              c.lineTo(r.x + (inverted ? -r.width : r.width), r.y + r.height);
            }
          }

          arrow.stroke.prepare();
          c.stroke();
        }
      }
    };
  }

  Marks.prototype = new Tee.Annotation();

  /**
   * @returns {Number} Returns the sum of all values in the array or typed-array parameter.
   * @param {Array|ArrayBuffer} a The array or typed-array to sum.
   */
  /*
function ArraySum(a){
  var sum=0, len=a.length;
  while(len--) sum+=a[len];
  return sum;
}
*/

  /**
   * @returns {Number} Returns the sum of all absolute values in the array or typed-array parameter.
   * @param {Array|ArrayBuffer} a The array or typed-array to do absolute sum.
   */
  function ArraySumAbs(a) {
    var sum = 0,
      len = a.length;
    while (len--) sum += a[len] > 0 ? a[len] : -a[len];
    return sum;
  }

  if (!("map" in Array.prototype)) {
    Array.prototype.map = function (mapper, that /*opt*/) {
      var other = new Array(this.length);
      for (var i = 0, n = this.length; i < n; i++)
        if (i in this) other[i] = mapper.call(that, this[i], i, this);

      return other;
    };
  }
  if (!("filter" in Array.prototype)) {
    Array.prototype.filter = function (filter, that /*opt*/) {
      var other = [],
        v;
      for (var i = 0, n = this.length; i < n; i++)
        if (i in this && filter.call(that, (v = this[i]), i, this))
          other.push(v);

      return other;
    };
  }

  /**
   * removes nulls that Math.max/min.apply doesn't handle correctly
   */
  function removeEmptyArrayElements(arr) {
    if (!isArray(arr)) {
      return arr;
    } else {
      return arr
        .filter(function (elem) {
          return elem !== null;
        })
        .map(removeEmptyArrayElements);
    }
  }

  function isArray(obj) {
    return obj && obj.constructor == Array;
  }

  /**
   * @returns {Number} Returns the maximum value in the array or typed-array parameter.
   * @param {Array|ArrayBuffer} a The array or typed-array of numbers.
   */
  function ArrayMax(a) {
    var arr = removeEmptyArrayElements(a);
    var max = -Number.MAX_VALUE;
    arr.forEach(function (e) {
      if (max < e) {
        max = e;
      }
    });
    if (Object.prototype.toString.call(max) === "[object Date]") {
      max = max.getTime();
    }
    return max;
  }
  /**
   * @returns {Number} Returns the minimum value in the array or typed-array parameter.
   * @param {Array|ArrayBuffer} a The array or typed-array of numbers.
   */
  function ArrayMin(a) {
    var arr = removeEmptyArrayElements(a);
    var min = Number.MAX_VALUE;
    arr.forEach(function (e) {
      if (min > e) {
        min = e;
      }
    });
    if (Object.prototype.toString.call(min) === "[object Date]") {
      min = min.getTime();
    }
    return min;
  }

  /**
   * @constructor
   * @class Base abstract class to define a series of data
   * @param {Object|Tee.Chart|Number[]} o An array of numbers, or a chart or datasource object.
   * @property {Tee.Chart} chart The parent chart this Series object belongs to.
   * @property {Number[]} data.values Array of numbers as main series data.
   * @property {String[]} data.labels Array of strings used to display at axis labels, legend and marks.
   * @property {Tee.Format} format Visual properties to display series data.
   * @property {Boolean} [visible=true] Determines if this series will be displayed or not.
   * @property {String} [cursor="default"] Defines the mouse cursor to show when mouse is over a series point.
   * @property {Object} data Contains all series data values, labels, etc.
   * @property {Tee.Series.Marks} marks Displays annotations near series points.
   * @property {Boolean} [colorEach="auto"] Paints points using series fill color, or each point with a different color
   * from series palette or chart palette color array.
   * @property {String} [horizAxis="bottom"] Defines the horizontal axis associated with this series.
   * @property {String} [vertAxis="left"] Defines the horizontal axis associated with this series.
   */
  Tee.Series = function (o, o2) {
    this.chart = null;
    this.data = { values: [], labels: [], source: null };

    this.sortedOptions = {
      sortedDrawAnimation: new Tee.Animation(),
      sortedValues: [],
      sortedValuesIndices: [],
      originalValues: [],
      sortedLabels: [],
      originalLabels: [],
      sorted: false,
      ascending: true,
      sorting: false,
      sortingAnimationType: "verticalchange",
    };
    this.yMandatory = true;
    this.horizAxis = "bottom";
    this.vertAxis = "left";
    this.legend = { visible: true };
    this.legendStrokeColor = false;

    this.sequential = true;

    var f = (this.format = new Tee.Format(this.chart)),
      ho = (this.hover = new Tee.Format(this.chart)),
      s = ho.shadow;
    this.sortedOptions.sortedDrawAnimation.duration = 500;
    this.sortedOptions.sortedDrawAnimation.mode = "linear";
    f.fill = "";
    f.stroke.fill = "";
    this.visible = true;

    // Hover
    ho.stroke.size = 0.3;
    ho.fill = "";
    ho.stroke.fill = "red";

    s.visible = true;
    s.blur = 10;
    s.width = 0;
    s.height = 0;

    this.cursor = "default";
    this.over = -1;

    this.marks = new Marks(this, this.chart);

    this.palette = new Tee.Palette();
    this.paletteName = "opera";
    this.themeName = "default";

    this.colorEach = "auto";
    this.useAxes = true;
    this.decimals = 2;

    this._paintAxes = true;
    this._paintWalls = true;

    this.sortValues = function () {
      this.sortedOptions.sorting = true;
      var indices = [],
        values = this.data.values,
        ascending = this.sortedOptions.ascending;
      this.sortedOptions.sortedLabels = [];
      for (var i = 0; i < this.data.values.length; i++) {
        indices.push(i);
      }
      indices.sort(function (a, b) {
        return ascending ? values[a] - values[b] : values[b] - values[a];
      });
      this.sortedOptions.originalValues = this.data.values.slice();
      this.sortedOptions.originalLabels = this.data.labels.slice();
      for (var i = 0; i < indices.length; i++) {
        this.sortedOptions.sortedLabels.push(
          this.data.labels[indices[i]]
            ? this.data.labels[indices[i]]
            : indices[i]
        );
      }
      this.sortedOptions.sortedValuesIndices = indices;
      this.sortedOptions.sortedValues = this.data.values
        .slice()
        .sort(function (a, b) {
          return ascending ? a - b : b - a;
        });
      this.sortedOptions.sorting = false;
      return this.sortedOptions.sortedValues.slice();
    };

    this.drawSortedValues = function (sorted) {
      var animation = this.sortedOptions.sortedDrawAnimation;
      if (
        this.sortedOptions.sorted != sorted &&
        !this.sortedOptions.sorting &&
        animation &&
        !animation.running
      ) {
        this.sortedOptions.sorted = sorted;
        var data = this.data;
        if (sorted) this.sortValues();
        var prevValues = sorted
          ? this.sortedOptions.originalValues.slice()
          : this.sortedOptions.sortedValues.slice();
        var values = this.data.values;
        var endValues = sorted
          ? this.sortedOptions.sortedValues.slice()
          : this.sortedOptions.originalValues.slice();

        if (
          !(
            values.length == endValues.length &&
            values.every(function (v, i) {
              return v === endValues[i];
            })
          )
        ) {
          animation.chart = this.chart;

          animation.doStep = function (f) {
            if (f < 1) {
              for (var i = 0; i < prevValues.length; i++) {
                values[i] = prevValues[i] + (endValues[i] - prevValues[i]) * f;
              }
            }
          };

          animation.onstop = function () {
            for (var i = 0; i < prevValues.length; i++) {
              values[i] = endValues[i];
            }
            animation.running = false;
          };

          animation.onstart = function () {
            animation.running = true;
          };
          animation.animate();
        }
        this.data.labels = sorted
          ? this.sortedOptions.sortedLabels.slice()
          : this.sortedOptions.originalLabels.slice();
        this.chart.draw();
      }
    };

    this.init = function (o, o2) {
      if (typeof o === "object") {
        if (o) {
          if (o instanceof Array) {
            this.data.values = o;

            if (o2 instanceof Array) this.data.labels = o2;
          } else if (o instanceof Tee.Chart) {
            this.chart = o;
            if (o2 instanceof Array) this.data.values = o2;
          } else {
            this.data.source = o;
            this.refresh();
          }
        }
      }
    };

    this.init(o, o2);

    /**
     * @returns {String} Returns the color of index point in series, using series palette or chart palette.
     */
    this.getFill = function (index, f) {
      var p = this.palette,
        c = p && p.colors ? p.get(index) : null;
      if (c === null)
        return this.isColorEach || !f ? this.chart.palette.get(index) : f.fill;
      else return c;
    };

    /**
     * @returns {boolean} Returns true when the index'th series value is null and should not be painted.
     */
    this.isNull = function (index) {
      return this.data.values[index] === null;
    };

    /**
     * @returns {CanvasGradient} Returns a canvas gradient using color, or color if gradient is not visible.
     */
    this.getFillStyle = function (r, color) {
      return f.gradient.visible ? f.gradient.create(r, color) : color;
    };

    this.title = "";

    this.titleText = function (index) {
      return this.title || "Series " + index.toString();
    };

    this.refresh = function (failure) {
      if (this.data.source) {
        if (this.data.source instanceof HTMLTextAreaElement) {
          parseText(this.data, this.data.source.value);

          if (this.chart) this.chart.draw();
        } else if (this.data.source instanceof HTMLInputElement) {
          Tee.doHttpRequest(
            this,
            this.data.source.value,
            function (target, data) {
              parseText(target.data, data);
              target.chart.draw();
            },
            function (status, statusText) {
              if (failure) failure(this, status, statusText);
            }
          );
        } else if (failure) failure(this);
      } else if (this.data.xml) {
        parseXML(this, this.data.xml);
        this.chart.draw();
      } else if (this.data.json) {
        parseJSON(this, this.data.json);
        this.chart.draw();
      }
    };

    /**
     * @returns {String} Returns the series index'th data label, or the value if no label exists at that index.
     */
    this.valueOrLabel = function (index) {
      var s = this.data.labels[index];

      if (!s || s === "") s = this.valueText(index);

      return s;
    };

    /**
     * @returns {String} Returns a percentual representation of the series index'th value, on total of series values.
     */
    this.toPercent = function (index) {
      var v = this.data.values;
      return (
        ((100 * Math.abs(v[index])) / ArraySumAbs(v)).toFixed(this.decimals) +
        " %"
      );
    };

    /**
     * @returns {String} Returns the text string to show at series marks, for a given series point index.
     */
    this.markText = function (index) {
      var m = this.marks,
        res = this.dataText(index, m.style, false);
      return m.ongettext ? m.ongettext(this, index, res) : res;
    };

    /**
     * @returns {Boolean} Returns if series is associated to axis, either horizontal or vertical.
     */
    this.associatedToAxis = function (axis) {
      if (axis.horizontal)
        return this.horizAxis == "both" || this._horizAxis == axis;
      else return this.vertAxis == "both" || this._vertAxis == axis;
    };

    this.bounds = function (r) {
      var h = this._horizAxis,
        v = this._vertAxis;

      r.x = h.calc(this.minXValue());
      r.width = h.calc(this.maxXValue()) - r.x;

      r.y = v.calc(this.maxYValue());
      r.height = v.calc(this.minYValue()) - r.y;
    };

    this.calcStack = function (index, p, value) {
      var sum = this.pointOrigin(index, false) + value,
        tmp,
        a = this.mandatoryAxis;

      p.x = this.notmandatory.calc(this.data.x ? this.data.x[index] : index);

      if (this.isStack100) {
        tmp = this.pointOrigin(index, true);
        p.y = tmp === 0 ? a.endPos : a.calc((sum * 100.0) / tmp);
      } else p.y = a.calc(sum);

      if (!this.yMandatory) {
        tmp = p.x;
        p.x = p.y;
        p.y = tmp;
      }
    };

    /**
     * @returns {Number} Returns the sum of all previous visible series index'th value, for stacking.
     */
    this.pointOrigin = function (index, sumAll) {
      var res = 0,
        t,
        s,
        li = this.chart.series.items,
        v,
        tmp;

      for (t = 0; t < li.length; t++) {
        s = li[t];

        if (!sumAll && s == this) break;
        else if (s.stacked != "no") {
          v = s.data.values;

          if (
            s.visible &&
            s.constructor == this.constructor &&
            v.length > index
          ) {
            tmp = v[index];

            // Protect against undefined (NaN)
            if (tmp !== undefined) res += sumAll && tmp < 0 ? -tmp : tmp;
          }
        }
      }

      return res;
    };

    this.doHover = function (index) {
      var o = this.chart;
      if (index != this.over) {
        if (o.onhover) o.onhover(this, index);

        this.over = index;

        if (this.hover.enabled)
          window.requestAnimFrame(function () {
            o.draw();
          });
      }
    };
  };

  /*
   * @private
   */
  Tee.Series.prototype.initZ = function (index, total) {
    var f = this.format;
    f.z = index / total;
    f.depth = 1 / total;
    this.marks.format.z = f.z + f.depth * 0.5;
  };

  /*
   * @private
   */
  Tee.Series.prototype.setChart = function (series, chart) {
    series.chart = chart;

    series.recalcAxes();

    series.format.setChart(chart);
    series.marks.setChart(chart);
    series.hover.setChart(chart);
  };

  Tee.Series.prototype.calc = function (index, p) {
    var d = this.data,
      x = this.notmandatory.calc(d.x ? d.x[index] : index),
      y = this.mandatoryAxis.calc(d.values[index]);

    p.x = this.yMandatory ? x : y;
    p.y = this.yMandatory ? y : x;
  };

  Tee.Series.prototype.recalcAxes = function () {
    var a = this.chart.axes;

    if (this.horizAxis instanceof Axis) this._horizAxis = this.horizAxis;
    else this._horizAxis = this.horizAxis == "top" ? a.top : a.bottom;

    if (this.vertAxis instanceof Axis) this._vertAxis = this.vertAxis;
    else this._vertAxis = this.vertAxis == "right" ? a.right : a.left;

    this.mandatoryAxis = this.yMandatory ? this._vertAxis : this._horizAxis;
    this.notmandatory = this.yMandatory ? this._horizAxis : this._vertAxis;
  };

  // Pending for solution (gauges.bounds) :
  Tee.Series.prototype.getRect = function () {
    return new Rectangle();
  };

  Tee.Series.prototype.clicked = function () {
    return -1;
  };

  Tee.Series.prototype.fixedFloatToLocal = function (value, decimals) {
    var fixed = value.toFixed(decimals);
    var localeVal = value.toLocaleString(this.chart.language);

    if (fixed.indexOf(".") != -1) {
      var n = 1.1;
      n = n.toLocaleString(this.chart.language).substring(1, 2);
      var fractions = fixed.substring(fixed.indexOf(".") + 1); //zero based idx
      return (
        localeVal.substring(
          0,
          localeVal.indexOf(n) == -1 ? localeVal.length : localeVal.indexOf(n)
        ) +
        n +
        fractions
      );
    } else return value;
  };

  /**
   * @returns {String} Returns the text string for a given series point index value.
   */
  Tee.Series.prototype.valueText = function (index) {
    var vv = this.data._old || this.data.values,
      d = vv[index];

    if (d) {
      if (d instanceof Date)
        return d.format ? d.format(this.dateFormat) : d.toString();
      else if (this.valueFormat) return d.toLocaleString(this.chart.language);
      else if (trunc(d) == d) return this.fixedFloatToLocal(d, 0).toString();
      else return this.fixedFloatToLocal(d, this.decimals).toString();
    } else return "0";
  };

  Tee.Series.prototype.labelOrTitle = function (index) {
    return this.data.labels[index] || this.title;
  };

  Tee.Series.prototype.mousedown = function () {
    return false;
  };

  Tee.Series.prototype.mousemove = function (p) {
    if (this.hover.enabled || this.cursor != "default") {
      var tmp = this.clicked(p);

      this.doHover(tmp);

      if (this.cursor != "default" && tmp != -1) {
        if (!this.chart.newCursor) this.chart.newCursor = this.cursor;
        return;
      }
    }

    var m = this.marks;

    if (m.visible) {
      var len = this.data.values.length,
        p2 = new Point(),
        t;

      for (t = 0; t < len; t += m.drawEvery)
        if (!this.isNull(t)) {
          this.markPos(t, p2);

          if (m.canDraw(p2.x, p2.y, t)) {
            if (m.bounds.contains(p)) {
              this.doHover(t);
              break;
            }
          }
        }
    }
  };

  Tee.Series.prototype.mouseout = function () {};

  Tee.Series.prototype.markPos = function (t, p) {
    this.calc(t, p);
    return false;
  };

  Tee.Series.prototype.drawMarks = function () {
    var len = this.data.values.length,
      p = new Point(),
      t,
      inv;

    for (t = 0; t < len; t += this.marks.drawEvery)
      if (!this.isNull(t)) {
        inv = this.markPos(t, p);
        this.marks.drawMark(p.x, p.y, t, inv);
      }
  };

  Tee.Series.prototype.horizMargins = function () {};
  Tee.Series.prototype.vertMargins = function () {};

  /**
   * @returns {Number} Returns the minimum value of series x values, or zero if no x values exist.
   */
  Tee.Series.prototype.minXValue = function () {
    return this.data.x && this.data.x.length > 0 ? ArrayMin(this.data.x) : 0;
  };

  /**
   * @returns {Number} Returns the minimum value of series data values, or zero if no values exist.
   */
  Tee.Series.prototype.minYValue = function () {
    var v = this.data.values;
    return v.length > 0 ? ArrayMin(v) : 0;
  };

  /**
   * @returns {Number} Returns the maximum value of series x values, or data length minus one, if no x values exist.
   */
  Tee.Series.prototype.maxXValue = function () {
    if (this.data.x) return this.data.x.length > 0 ? ArrayMax(this.data.x) : 0;
    else {
      var len = this.data.values.length;
      return len === 0 ? 0 : len - 1;
    }
  };

  /**
   * @returns {Number} Returns the maximum value of series values, or zero if no values exist.
   */
  Tee.Series.prototype.maxYValue = function () {
    var v = this.data.values,
      l = v.length,
      t,
      value,
      res;

    if (l > 0) {
      res = ArrayMax(v);

      if (res !== res) {
        // isNan, protect against
        for (t = 0; t < l; t++) {
          value = v[t];

          if (value !== undefined) {
            if (res !== res) res = value;
            else if (value > res) res = value;
          }
        }
      }

      return res === res ? res : 0;
    } else return 0;
  };

  Tee.Series.prototype.calcColorEach = function () {
    this.isColorEach = this.colorEach == "yes";
  };

  /**
   * @returns {Number} Returns the maximum of all series values, or sum of all stacked values.
   */
  Tee.Series.prototype.stackMaxValue = function () {
    if (this.stacked == "100") return 100;
    else {
      var temp = Tee.Series.prototype.maxYValue;

      if (this.stacked == "no") return temp.call(this);
      else {
        var res = temp.call(this),
          v = this.data.values,
          len = v.length,
          value;

        while (len--) {
          value = v[len];
          if (value === undefined) value = 0;
          res = Math.max(res, this.pointOrigin(len, false) + value);
        }

        return res;
      }
    }
  };

  /**
   * @returns {String} Returns the text string to show for a given series point index.
   * @param {Number} index The point position in series data array.
   * @param {String} style Defines how text is returned: "auto", "value", "percent", "percentlabel",
   * "valuelabel", "label", "index", "labelvalue", "labelpercent", "valuepercent"
   */
  Tee.Series.prototype.dataText = function (index, style, title, asArray) {
    function calcRet(a, b) {
      if (asArray) return l ? [a, b] : [a, ""];
      else return a + (b ? " " + b : "");
    }

    var l = title ? this.labelOrTitle(index) : this.data.labels[index];

    if (style == "value") return this.valueText(index);
    else if (style == "percent") return this.toPercent(index);
    else if (style == "percentlabel") return calcRet(this.toPercent(index), l);
    else if (style == "valuelabel" || style == "auto")
      return calcRet(this.valueText(index), l);
    else if (style == "label") return l || "";
    else if (style == "index") return index.toFixed(0);
    else if (style == "labelvalue") return calcRet(l, this.valueText(index));
    else if (style == "labelpercent") return calcRet(l, this.toPercent(index));
    else if (style == "valuepercent")
      return this.valueText(index) + " " + this.toPercent(index);
    else return this.valueOrLabel(index);
  };

  /**
   * @returns {String} Returns the text string to show for a given series point index.
   * @param {Number} index The point position in series data array.
   * @param {String} style Defines how text is returned: "auto", "value", "percent", "percentlabel",
   * "valuelabel", "label", "index", "labelvalue", "labelpercent", "valuepercent"
   */
  Tee.Series.prototype.legendText = Tee.Series.prototype.dataText;

  /**
   * @returns {Number} Returns the number of series data values.
   */
  Tee.Series.prototype.count = function () {
    return this.data.values.length;
  };

  /**
   * @returns {Number} Returns the number of items to show at legend.
   */
  Tee.Series.prototype.legendCount = function () {
    return this.count();
  };

  /**
   * @returns {Color} Returns the color of index'th legend symbol.
   */
  Tee.Series.prototype.legendColor = function (index) {
    return this.isColorEach && index != -1
      ? this.getFill(index)
      : this.legendStrokeColor
      ? this.format.stroke.fill
      : this.format.fill;
  };

  Tee.Series.prototype.addRandom = function (count, range, x) {
    if (!range) range = 1000;
    if (!count) count = 5;

    var d = this.data;
    d.values.length = count;

    if (x) d.x = new Array(count);

    if (count > 0) {
      d.values[0] = Math.random() * range;

      if (x) d.x[0] = Math.random() * range;

      for (var t = 1; t < count; t++) {
        d.values[t] = d.values[t - 1] + Math.random() * range - range * 0.5;
        if (x) d.x[t] = Math.random() * range;
      }
    }

    return this;
  };

  /**
   * @returns {Array} Returns an array of series data indices sorted according to sortBy parameter.
   */
  Tee.Series.prototype.doSort = function (sortBy, ascending) {
    if (sortBy == "none") return null;
    else {
      var d = this.data.values,
        len = d.length,
        sorted = new Array(len),
        t = 0;
      for (; t < len; t++) sorted[t] = t;

      if (sortBy == "labels") {
        d = this.data.labels;

        var A,
          B,
          before = ascending ? -1 : 1,
          after = ascending ? 1 : -1;

        sorted.sort(function (a, b) {
          A = d[a].toLowerCase();
          B = d[b].toLowerCase();
          return A < B ? before : A == B ? 0 : after;
        });
      } else
        sorted.sort(
          ascending
            ? function (a, b) {
                return d[a] - d[b];
              }
            : function (a, b) {
                return d[b] - d[a];
              }
        );

      return sorted;
    }
  };

  /**
   * @memberOf Tee.Chart
   * @constructor
   * @class Contains a list of chart series objects
   * @param {Tee.Chart} chart The parent chart this list of series belongs to.
   * @property {Tee.Series[]} items The array containing series instances.
   */
  function SeriesList(chart) {
    this.chart = chart;
    this.items = [];

    /**
     * @returns {Number} Returns the total number of series in chart, visible or not.
     */
    this.count = function () {
      return this.items.length;
    };

    /**
     * @returns {Boolean} Returns if {@link Tee.Point} p parameter is over any series point.
     */
    this.clicked = function (p) {
      var done = false;

      this.each(function (s) {
        if (s.visible && s.onclick) {
          var index = s.clicked(p);
          if (index != -1) done = s.onclick(s, index, p.x, p.y);
        }
      });

      return done;
    };

    this.mousedown = function (event) {
      for (var t = 0, s, done = false; (s = this.items[t++]); )
        if (s.visible) {
          if (s.mousedown(event)) done = true;
        }

      return done;
    };

    this.mousemove = function (p) {
      for (var t = 0, s; (s = this.items[t++]); ) if (s.visible) s.mousemove(p);

      /*
    var len=this.items.length, s;

    while(len--) {
      s=this.items[len];
      if (s.visible)
          s.mousemove(p);
    }
    */
    };

    this.mouseout = function () {
      this.each(function (s) {
        if (s.visible) s.mouseout();
      });
    };

    /**
     * Counts how many visible series exist of the same class type.
     * @returns {Number} Returns the number of visible series in chart of the same type as this.
     */
    this.visibleCount = function (s, c, res) {
      var r = 0,
        it = this.items,
        len = it.length,
        i;

      while ((len--, (i = it[len])))
        if (i.visible && (!c || i instanceof c)) {
          if (res && i == s) res.index = r;
          r++;
        }
      if (res) {
        res.total = r;
        res.index = r - 1 - res.index;
      }
      return r;
    };

    this.beforeDraw = function () {
      this.each(function (s) {
        if (s.useAxes) s.recalcAxes();

        s.calcColorEach();
      });
    };

    /**
     * @returns {Boolean} Returns if any visible series in chart needs axes to be represented.
     */
    this.anyUsesAxes = function () {
      var len = this.items.length,
        s;
      while (len--) {
        s = this.items[len];
        if (s.visible && s.useAxes) return true;
      }

      return false;
    };

    /**
     * @returns {Tee.Series} Returns the first visible series in chart, or null if any.
     */
    this.firstVisible = function () {
      for (var t = 0, s; (s = this.items[t++]); ) if (s.visible) return s;
      return null;
    };

    /**
     * Calculates the maximum amount of vertical margins in pixels from all series.
     * @returns {Tee.Point} Returns the maximum top/bottom distance in pixels that all series need to be separated from axes.
     */
    this.vertMargins = function () {
      var result,
        li = this.items,
        len = li.length,
        s,
        t;

      if (len > 0) {
        result = { x: 0, y: 0 };
        s = { x: 0, y: 0 };
        li[0].vertMargins(result);

        for (t = 1; t < len; t++) {
          if (li[t].data.values.length > 0) {
            s.x = s.y = 0;
            li[t].vertMargins(s);
            if (s.x > result.x) result.x = s.x;
            if (s.y > result.y) result.y = s.y;
          }
        }
      }
      return result;
    };

    /**
     * Calculates the maximum amount of horizontal margins in pixels from all series
     * @returns {Tee.Point} Returns the maximum left/right distance in pixels that all series need to be separated from axes.
     */
    this.horizMargins = function () {
      var result,
        li = this.items,
        len = li.length,
        s,
        t;

      if (len > 0) {
        result = { x: 0, y: 0 };
        s = { x: 0, y: 0 };
        li[0].horizMargins(result);

        for (t = 1; t < len; t++) {
          if (li[t].data.values.length > 0) {
            s.x = s.y = 0;
            li[t].horizMargins(s);

            if (s.x > result.x) result.x = s.x;
            if (s.y > result.y) result.y = s.y;
          }
        }
      }
      return result;
    };

    /**
     * @returns {Number} Returns the minimum of all visible non-empty series associated to axis, minimum x values.
     */
    this.minXValue = function (axis) {
      var result = Infinity,
        v;
      this.eachAxis(axis, function (s) {
        v = s.minXValue();
        if (v < result) result = v;
      });
      return result;
    };

    /**
     * @returns {Number} Returns the minimum of all visible series mininum data values.
     */
    this.minYValue = function (axis) {
      var result = Infinity,
        v;
      this.eachAxis(axis, function (s) {
        v = s.minYValue();
        if (v < result) result = v;
      });
      return result;
    };

    /**
     * @returns {Number} Returns the maximum of all visible series maximum x values.
     */
    this.maxXValue = function (axis) {
      var result = -Infinity,
        v;
      this.eachAxis(axis, function (s) {
        v = s.maxXValue();
        if (v > result) result = v;
      });
      return result;
    };

    /**
     * @returns {Number} Returns the maximum of all visible series maximum data values.
     */
    this.maxYValue = function (axis) {
      var result = -Infinity,
        v;
      this.eachAxis(axis, function (s) {
        v = s.maxYValue();
        if (v > result) result = v;
      });
      return result;
    };

    function axisStrokeSize(axis) {
      if (axis.visible && axis.firstSeries) {
        var s = axis.format.stroke;
        return s.fill === "" ? 0 : s.size * 0.5;
      } else return 0;
    }

    this.draw = function () {
      var len = this.items.length,
        ch = this.chart,
        c = ch.ctx,
        a = ch.aspect,
        t,
        s;

      if (len > 0) {
        for (t = 0; t < len; t++) {
          s = this.items[t];
          if (s.visible && s.beforeDraw) s.beforeDraw();
        }

        var shouldClip = a.clip && this.anyUsesAxes(),
          axes = ch.axes;

        if (shouldClip) {
          var chr = ch.chartRect,
            ax = axisStrokeSize(axes.left),
            ay = axisStrokeSize(axes.top),
            r = {
              x: chr.x + ax,
              y: chr.y + ay,
              width: chr.width - axisStrokeSize(axes.right),
              height: chr.height - axisStrokeSize(axes.bottom),
            };

          a.clipRect(r);
        }

        try {
          var groups = c.beginParent;

          for (t = 0; t < len; t++) {
            s = this.items[t];

            if (s.visible) {
              var old = c.globalAlpha;
              c.globalAlpha = 1 - s.format.transparency;

              if (s.transform) {
                c.save();
                s.transform();
              }

              s.visual = groups ? c.beginParent() : null;

              if (s.onbeforedraw) s.onbeforedraw(s);

              s.draw();

              if (groups) c.endParent();

              if (s.ondraw) s.ondraw(s);

              if (s.transform) c.restore();

              c.globalAlpha = old;
            }
          }
        } finally {
          if (shouldClip)
            //a.clipRect(ch.bounds);
            c.restore();
        }

        for (t = 0; t < len; t++) {
          s = this.items[t];

          if (s.visible && s.marks.visible) {
            if (s.transform) {
              c.save();
              s.transform();
            }

            s.drawMarks();

            if (s.transform) c.restore();
          }
        }
      }
    };
  }

  /**
   * @type SeriesList
   */
  /* Calls f function parameter for each series in list */
  SeriesList.prototype.each = function (f) {
    var l = this.items.length,
      t = 0;
    for (; t < l; t++) f(this.items[t]);
  };

  SeriesList.prototype.eachAxis = function (axis, func) {
    var len = this.items.length,
      s;
    while (len--) {
      s = this.items[len];
      if (
        s.visible &&
        (!axis || s.associatedToAxis(axis)) &&
        (s.__alwaysDraw || s.count() > 0)
      )
        func(s);
    }
  };

  /**
   * @memberOf Tee.Chart
   * @constructor
   * @class Contains four axis objects: left, top, right and bottom
   * @property {Boolean} [visible=true] Draws or not all the chart axis objects.
   * @property {Number} [transparency=0] Applies transparency to all axes in chart, from 0 to 1.
   * @param {Tee.Chart} chart The parent chart this axes object belongs to.
   */
  function Axes(chart) {
    this.chart = chart;
    this.visible = true;

    this.transparency = 0;

    /**
     * @public
     * @type Tee.Chart.Axis
     */
    this.left = new Axis(chart, false, false);

    /**
     * @public
     * @type Tee.Chart.Axis
     */
    this.top = new Axis(chart, true, true);

    /**
     * @public
     * @type Tee.Chart.Axis
     */
    this.right = new Axis(chart, false, true);

    /**
     * @public
     * @type Tee.Chart.Axis
     */
    this.bottom = new Axis(chart, true, false);

    this.items = [this.left, this.top, this.right, this.bottom];
    this.each = function (f) {
      for (var t = 0, a; (a = this.items[t++]); ) f.call(a);
    };

    /**
     * Creates and adds a new custom Axis
     */
    this.add = function (horiz, other) {
      var a = new Axis(chart, horiz, other);
      a.custom = true;
      this.items.push(a);
      return a;
    };
  }

  /**
   * @example
   * var Chart1 = new Tee.Chart("canvas");
   * Chart1.addSeries(new Tee.Bar([1,2,3,4]));
   * Chart1.draw();
   * @constructor
   * @class The main Chart class
   * @param {String|HTMLCanvasElement} [canvas] Optional canvas id or <a href="https://www.w3.org/wiki/HTML/Elements/canvas">element</a>.
   * @property {HTMLCanvasElement} canvas The <a href="https://www.w3.org/wiki/HTML/Elements/canvas">canvas</a> where this chart will paint to.
   * @property {Tee.Rectangle} bounds The rectangle where this chart will be painted inside canvas.
   * @property {Tee.Palette} palette The list of colors to use as default colors for series and points.
   * @property {Tee.Chart.Aspect} aspect Contains properties related to 3D and graphics parameters.
   * @property {Tee.Chart.Panel} panel Contains properties used to fill the chart background.
   * @property {Tee.Chart.Walls} walls Contains properties used to draw chart walls around axes.
   * @property {Tee.Chart.Axes} axes Contains a list of axis used to draw series.
   * @property {Tee.Chart.Legend} legend Contains properties to control the legend, a panel showing the list of series or values.
   * @property {Tee.Chart.SeriesList} series Contains a list of Tee.Series objects that belong to this chart.
   * @property {Tee.Chart.Title} title Properties to draw text at top side of chart.
   * @property {Tee.Chart.Title} footer Properties to draw text at bottom side of chart.
   * @property {Tee.Chart.Zoom} zoom Properties to control mouse/touch dragging to zoom chart axes scales.
   * @property {Tee.Chart.Scroll} scroll Properties to control mouse/touch dragging to scroll or pan contents inside chart axes.
   * @property {Tee.Chart.Tools} tools Contains a list of Tee.Tool objects that belong to this chart.
   */
  Tee.Chart = function (canvas, data, type) {
    var ua =
      typeof navigator != "undefined" ? navigator.userAgent.toLowerCase() : "";
    /**
     * @constant
     * @private
     */
    this.isChrome = ua.indexOf("chrome") > -1;
    /**
     * @constant
     * @private
     */
    this.isAndroid = ua.indexOf("android") > -1;
    /**
     * @constant
     * @private
     */
    this.isMozilla =
      typeof window !== "undefined" && window.mozRequestAnimationFrame;

    this.language = window.navigator.userLanguage || window.navigator.language;

    if (canvas) {
      if (
        typeof HTMLCanvasElement !== "undefined" &&
        canvas instanceof HTMLCanvasElement
      )
        this.canvas = canvas;
      else if (typeof canvas == "string")
        this.canvas = document.getElementById(canvas);
      else this.canvas = canvas;
    }

    if (!this.canvas) {
      this.canvas = document.createElement("canvas");
      this.canvas.width = 600;
      this.canvas.height = 400;
    }

    var c = this.canvas;
    var isNLc = false;

    this.__webgl = c.__webgl;

    if (c.__webgl || c.clientWidth === 0)
      this.bounds = new Rectangle(0, 0, c.width, c.height);
    else {
      this.bounds = new Rectangle(0, 0, c.clientWidth, c.clientHeight);
      c.width = c.clientWidth;
      c.height = c.clientHeight;
    }

    this.chartRect = new Rectangle();
    this.chartRect.automatic = true;
    this.chartRect.setFrom(this.bounds);

    this.palette = new Tee.Palette([
      "#4466a3",
      "#f39c35",
      "#f14c14",
      "#4e97a8",
      "#2b406b",
      "#1d7b63",
      "#b3080e",
      "#f2c05d",
      "#5db79e",
      "#707070",
      "#f3ea8d",
      "#b4b4b4",
    ]);

    /**
     * @memberOf Tee.Chart
     * @class Contains properties related to canvas and 2D / 3D
     * @param {Tee.Chart} chart The parent chart this aspect object belongs to.
     * @property {Boolean} clip When true, series contents will be restricted to paint inside axes boundaries.
     */
    this.aspect = {
      chart: this,
      view3d: this.__webgl,
      ortogonal: !this.__webgl,
      rotation: 0,
      elevation: 315,
      perspective: 50,
      clip: true,

      _orthox: 10,
      _orthoy: 8,

      /**
       * @param {Tee.Rectangle} r The rectangle object to apply clipping.
       */
      clipRect: function (r) {
        var c = this.chart.ctx;
        c.save();

        c.beginPath();

        if (this.view3d)
          c.rect(
            r.x,
            r.y - this._orthoy,
            r.width + this._orthox,
            r.height + this._orthoy
          );
        else c.rect(r.x, r.y, r.width, r.height);

        c.clip();
        //c.closePath();
      },
    };

    var aspect = this.aspect;

    /*
     * Properties to paint the chart background
     */
    this.panel = new Panel(this);

    /**
     * @memberOf Tee.Chart
     * @constructor
     * @class Contains left, right, bottom and back wall objects
     * @param {Tee.Chart} chart The parent chart this walls object belongs to.
     * @property {Boolean} [visible=true] Determines if walls will be displayed or not.
     * @property {Tee.Chart.Wall} back Visual properties to paint the back wall.
     */
    this.walls = {
      chart: this,
      visible: true,
      left: new Wall(this),
      right: new Wall(this),
      bottom: new Wall(this),
      back: new Wall(this),

      draw: function (r, aspect) {
        var old,
          ctx = this.chart.ctx,
          t = this.transparency,
          groups = ctx.beginParent;

        this.visual = groups ? ctx.beginParent() : null;

        if (t > 0) {
          old = ctx.globalAlpha;
          ctx.globalAlpha = (1 - t) * old;
        }

        var backBounds = this.back.bounds;
        backBounds.setFrom(r);

        if (aspect.view3d) {
          var bottomsize = this.bottom.visible ? this.bottom.size : 0,
            leftsize = this.left.size;

          if (leftsize > 0) {
            backBounds.x -= leftsize;
            backBounds.width += leftsize;
          }

          if (bottomsize > 0) backBounds.height += bottomsize;

          this.left.bounds.set(
            r.x - leftsize,
            r.y,
            leftsize,
            r.height + bottomsize
          );
          this.bottom.bounds.set(r.x, r.getBottom(), r.width, bottomsize);

          this.back.format.depth = this.back.size;
        }

        !this.back.visible || this.back.draw();

        if (this.chart.aspect.view3d) {
          if (this.left.visible) this.left.draw();
          if (this.bottom.visible) this.bottom.draw();
          if (this.right.visible) this.right.draw();
        }

        if (t > 0) ctx.globalAlpha = old;

        if (groups) ctx.endParent();
      },
    };

    var bf = this.walls.back.format;
    bf.fill = "rgb(240,240,240)";
    bf.shadow.visible = true;
    bf.z = 1;

    var lw = this.walls.left;
    lw.format.fill = "#BBAA77";
    lw.format.depth = 1;
    lw.size = 2;

    var bof = this.walls.bottom;
    bof.format.depth = 1;
    bof.size = 2;

    this.walls.right.visible = false;

    /*
     * Four axes
     */
    this.axes = new Axes(this);

    /*
     * Properties to paint a list of series or values.
     */
    this.legend = new Legend(this);

    /*
     * List of Tee.Series objects that this chart contains.
     */
    this.series = new SeriesList(this);

    this.title = new Title(this, "blue");
    this.title.text = "TeeChart";
    this.title.format.z = 1;

    this.subtitle = new Title(this, "blue");
    this.subtitle.format.z = 1;

    this.footer = new Title(this, "red");
    this.footer.format.z = 0;

    this.subfooter = new Title(this, "red");
    this.subfooter.format.z = 0;

    this.zoom = new Zoom(this);
    this.scroll = new Scroll(this);

    this.tools = new Tools(this);

    /**
     * @private
     */
    this.oldPos = new Point();

    /**
     * @private
     * @returns {Tee.Point} Returns the xy local coordinates from a mouse or touch event
     */
    this.calcMouse = function (e, p) {
      p.x = e.clientX;
      p.y = e.clientY;

      var element = this.canvas,
        r;

      // IE, Moz3+, Chr, Op9.5+, Saf4+
      if (element.getBoundingClientRect) {
        r = element.getBoundingClientRect();
        p.x -= r.left;
        p.y -= r.top;
      } //earlier Moz.
      else if (element.offsetParent)
        do {
          p.x -= element.offsetLeft;
          p.y -= element.offsetTop;
          element = element.offsetParent;
        } while (element);
    };

    var pMove = new Point(0, 0);

    this.domousemove = function (event) {
      var c = this.chart;
      if (!c.ctx) return false;

      event = event || window.event;

      if (event.touches) event = event.touches[event.touches.length - 1];

      c.calcMouse(event, pMove);

      if (c.scroll.active) {
        var d = c.scroll.direction,
          both = d == "both",
          delta;

        if (both || d == "horizontal") {
          delta = c.axes.bottom.fromSize(c.oldPos.x - pMove.x);
          c.axes.top.scroll(delta);
          c.axes.bottom.scroll(delta);
        }

        if (both || d == "vertical") {
          delta = -c.axes.left.fromSize(c.oldPos.y - pMove.y);
          c.axes.left.scroll(delta);
          c.axes.right.scroll(delta);
        }

        c.oldPos.x = pMove.x;
        c.oldPos.y = pMove.y;

        c.scroll.done = true;

        if (c.onscroll) c.onscroll(event);

        if (c.scroll.done)
          window.requestAnimFrame(function () {
            c.draw();
          });

        return false;
      } else if (c.zoom.active) {
        if (pMove.x != c.oldPos.x || pMove.y != c.oldPos.y) {
          c.zoom.change(pMove);

          window.requestAnimFrame(function () {
            c.draw();
          });

          c.zoom.done = true;
        }

        return false;
      } else {
        c.newCursor = null;
        c.tools.mousemove(pMove);
        c.series.mousemove(pMove);
        c.legend.mousemove(pMove);
        c.title.mousemove(pMove);
        if (c.mousemove) c.mousemove(pMove);

        var s = this.chart.canvas.style;

        if (c.newCursor) {
          if (s.cursor != c.newCursor) {
            c.oldCursor = s.cursor;
            s.cursor = c.newCursor;
          }
        } else if (c.oldCursor !== undefined && s.cursor != c.oldCursor)
          s.cursor = c.oldCursor;

        return true;
      }
    };

    var p = new Point(0, 0);

    this.domousedown = function (event) {
      event = event || window.event;
      var done = false,
        c = this.chart;

      c.calcMouse(event.touches ? event.touches[0] : event, p);
      var inRect = c.series.anyUsesAxes() && c.chartRect.contains(p);

      doubleTap(c);
      if (c.zoom.enabled) {
        twoFingersZoom(c, c.zoom);
      }

      var inRect = c.series.anyUsesAxes() && c.chartRect.contains(p);

      if (event.touches) {
        //alert("touch! "+event.touches.length.toString());
        // two-finger pinch to zoom, one finger to scroll

        if (event.touches.length > 1) {
          // c.zoom.active=c.zoom.enabled && inRect;
          //if (c.zoom.active)
          c.scroll.active = false;
        } else {
          c.scroll.active = c.scroll.enabled && inRect;
          if (c.scroll.active) c.zoom.active = false;
        }
      } else {
        c.zoom.active =
          event.button == c.zoom.mouseButton && c.zoom.enabled && inRect;
        c.scroll.active =
          event.button == c.scroll.mouseButton && c.scroll.enabled && inRect;
      }

      c.zoom.done = false;
      c.scroll.done = false;
      c.oldPos = p;

      if (event.button === 0) {
        // c.zoom.mouseButton)
        done = c.tools.mousedown(event);
        if (!done) {
          done = c.series.mousedown(event);

          if (!done) done = c.legend.mousedown(event);
        }
        if (!done) if (c.mousedown) done = c.mousedown(event);

        c.canvas.oncontextmenu = null;
      } else if (event.button == 2)
        c.canvas.oncontextmenu = function () {
          return false;
        };

      if (done) c.zoom.active = c.scroll.active = false;
      else done = c.zoom.active || c.scroll.active;

      if (event.preventDefault) event.preventDefault();
      else event.cancelBubble = true;

      if (done) {
        // IE < 9 : "fromElement"
        var target = event.target || event.fromElement;

        if (target && target.setPointerCapture && event.pointerId)
          target.setPointerCapture(event.pointerId);
      }

      return !done;
    };

    this.domouseup = function (event) {
      event = event || window.event;
      var c = this.chart,
        done;
      c.zoom.active = false;
      c.scroll.active = false;

      if ((c.zoom.done) && (!c.zoom.touching)) {
        if (c.zoom.apply()) if (c.onzoom) c.onzoom();

        c.draw();

        done = true;
      } else {
        done = c.scroll.done;
        if (!done) {
          done = c.series.clicked(c.oldPos);
          if (!done) {
            done = c.tools.clicked(c.oldPos);
            if (!done) {
              done = c.title.clicked(c.oldPos);
              if (done && c.title.onclick) c.title.onclick(c.title);
            }
          }
          if (!done) if (c.mouseup) done = c.mouseup(event);
        }
      }

      c.zoom.old = null;

      c.zoom.done = false;
      c.scroll.done = false;

      if (done)
        if (event.preventDefault) event.preventDefault();
        else event.cancelBubble = true;
      else c.canvas.oncontextmenu = null;

      // IE < 9 : "fromElement"
      var target = event.target || event.fromElement;

      if (target && target.releasePointerCapture && event.pointerId)
        target.releasePointerCapture(event.pointerId);
    };

    /*
  if (c.addEventListener) {
    c.addEventListener("mousedown", this.domousedown, false);
    c.addEventListener("touchstart", this.domousedown, false);
    c.addEventListener("mousemove", this.domousemove, false);
    c.addEventListener("touchmove", this.domousemove, false);
    c.addEventListener("mouseup", this.domouseup, false);
    c.addEventListener("touchstop", this.domouseup, false);
  }
  else {
    // IE <9 attachEvent

    if (c.attachEvent) {
      c.attachEvent("onmousedown", this.domousedown);
      c.attachEvent("ontouchstart", this.domousedown);
      c.attachEvent("onmousemove", this.domousemove);
      c.attachEvent("ontouchmove", this.domousedown);
      c.attachEvent("onmouseup", this.domouseup);
      c.attachEvent("ontouchstop", this.domousedown);
    }
  }
  */

    c.onpointerdown = c.ontouchstart = this.domousedown;
    c.onpointerup = c.ontouchend = this.domouseup;
    c.onpointermove = c.ontouchmove = this.domousemove;

    // Mouse wheel default to zoom / unzoom axes:

    this._doWheel = function (event) {
      function applyAxis(a) {
        var axisrange = a.maximum - a.minimum;

        if (axisrange > 0) {
          var range = delta * axisrange * 0.05;
          a.setMinMax(a.minimum + range, a.maximum - range);
        }
      }

      var c = this.chart;

      if (!c.zoom.wheel.enabled) {
        for (var t = 0, s; (s = c.tools.items[t++]); )
          if (s instanceof Tee.ToolTip && s.active) s.hide();

        return;
      }

      event = event || window.event;

      var delta =
        c.zoom.wheel.factor *
        (event.wheelDelta
          ? event.wheelDelta / 120
          : event.detail
          ? -event.detail / 3
          : 0);

      if (Math.abs(delta) > 0) {
        var p = { x: 0, y: 0 };
        c.calcMouse(event, p);

        if (c.chartRect.contains(p)) {
          c.axes.each(function () {
            applyAxis(this);
          });

          c.draw();

          event.returnValue = false;
          if (event.preventDefault) event.preventDefault();
        }

        for (var t = 0, s; (s = c.tools.items[t++]); )
          if (s instanceof Tee.ToolTip && s.active) s.mousemove(p);
      }
    };

    if (c.addEventListener)
      c.addEventListener("DOMMouseScroll", this._doWheel, false);

    c.onmousewheel = this._doWheel;

    // Alternative to canvas lack of mouse setCapture:

    c.onmouseout = function (e) {
      if (e && !e.target.setCapture) this.chart.scroll.active = false;

      this.chart.series.mouseout();
      this.chart.tools.mouseout();
    };

    /**
     * @returns {Tee.Series} Returns the series parameter
     * @param {Tee.Series} series The series object to add to chart.
     */
    this.addSeries = function (series) {
      series.setChart(series, this);
      if (series.donutArray != null && series.donutArray !== "undefined")
        series.linkDonutsToChart();

      var li = this.series.items,
        n = li.indexOf(series);

      if (n == -1) n = li.push(series) - 1;

      if (series.title === "") series.title = "Series" + (1 + n).toString();

      if (series.format.fill === "") series.format.fill = this.palette.get(n);

      return series;
    };

    this.removeSeries = function (series) {
      var li = this.series.items,
        n = li.indexOf(series);
      if (li != -1) li.splice(n, 1);
    };

    c.chart = this;

    function newSeries(v) {
      var St = type || Tee.Bar,
        s = c.chart.addSeries(new St(c.chart));
      s.data.values = v;
    }

    if (data && data.length > 0) {
      if (data[0] instanceof Array)
        for (var t = 0; t < data.length; t++) newSeries(data[t]);
      else newSeries(data);
    }

    /**
     * @returns {Tee.Series} Returns the index'th series in chart series list
     * @param {Integer} index The index of the chart series list to obtain.
     */
    this.getSeries = function (index) {
      return this.series.items[index];
    };

    /**
     * Main Chart draw method. Repaints all chart contents.
     */
    this.draw = function (ctx) {
      var series = this.series,
        r = this.chartRect;

      this.ctx =
        ctx || (this.canvas.getContext ? this.canvas.getContext("2d") : null); //,{alpha:false} (opaque canvas)
      ctx = this.ctx;

      if (!ctx) throw "Canvas does not provide Context";

      if (Tee.Scroller && this instanceof Tee.Scroller) {
        ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      }

      if (r.automatic) r.setFrom(this.bounds);

      var len = series.items.length,
        s,
        paintAxes = false,
        paintWalls = false,
        maxZ = 1,
        visCount = series.visibleCount(),
        tmp = 0;

      while (len--) {
        s = series.items[len];

        if (s.visible) {
          s.initZ(tmp, visCount);
          tmp++;

          if (s._paintAxes) paintAxes = true;
          if (s._paintWalls) paintWalls = true;

          if (s.maxZ && s.maxZ > maxZ) maxZ = s.maxZ;
        }
      }

      this.walls.left.format.depth = maxZ;
      this.walls.bottom.format.depth = maxZ;
      this.walls.back.format.z = maxZ;

      this.panel.draw();

      if (r.automatic) this.panel.margins.apply(r);

      this.title.tryDraw(true);
      this.subtitle.tryDraw(true);
      this.footer.tryDraw(false);
      this.subfooter.tryDraw(false);

      series.beforeDraw();

      if (this.legend.visible) {
        this.legend.calcrect();
        this.legend.draw();
      }

      if (aspect.view3d && !this.__webgl) {
        r.y += aspect._orthoy;
        r.height -= aspect._orthoy;
        r.width -= aspect._orthox;
      }

      var ax = this.axes,
        oldt;

      if (this.series.anyUsesAxes()) {
        ax.each(Axis.adjustRect);
        ax.each(Axis.calcRect);

        if (this.walls.visible && paintWalls) this.walls.draw(r, this.aspect);

        if (ax.visible && paintAxes) {
          if (ax.transparency > 0) {
            oldt = ctx.globalAlpha;
            ctx.globalAlpha = (1 - ax.transparency) * oldt;
          }

          var groups = ctx.beginParent;
          ax.visual = groups ? ctx.beginParent() : null;

          ax.each(Axis.draw);

          if (groups) ctx.endParent();

          if (ax.transparency > 0) ctx.globalAlpha = oldt;
        }
      }

      this.series.draw();
      this.tools.draw();

      if (this.zoom.active && !this.zoom.touching) this.zoom.draw();

      if (this.ondraw) this.ondraw(this);

      this.isNLc = false;
      if (this.isNLc) {
        var f = new Tee.Format(this);
        var msg = "TeeChart Evaluation (c)Steema Software 2021";
        var tw;
        var ctx = this.ctx;
        ctx.save();
        ctx.font = "13px Courier";
        tw = f.textWidth(msg);
        var rText = {
          x: this.chartRect.x + this.chartRect.width / 2 - tw / 2,
          y: this.bounds.height / 2,
          width: tw,
          height: f.textHeight("H"),
        };
        ctx.translate(
          this.chartRect.x + this.chartRect.width / 2,
          this.bounds.height / 2
        );
        ctx.rotate((-30 * Math.PI) / 180);
        ctx.translate(
          -(this.chartRect.x + this.chartRect.width / 2),
          -(this.bounds.height / 2)
        );
        f.font.fill = "rgba(64,108,128, 0.55)";
        f.drawText(rText, msg);
        ctx.restore();
      }
    };

    /**
     * Paints chart to image parameter, as PNG or JPEG picture made from canvas.
     * @param {String} image The id of an Image HTML component.
     * @param {String} format Can be "image/png" or "image/jpeg"
     * @param {Number} quality From 0% to 100%, jpeg compression quality.
     */
    this.toImage = function (image, format, quality) {
      var i = document.getElementById(image);
      if (i)
        i.src =
          format !== ""
            ? this.canvas.toDataURL(format, quality)
            : this.canvas.toDataURL();
    };
  };

  /**
   * @constructor
   * @augments Tee.Series
   * @class Base abstract class to draw data as vertical or horizontal bars
   * @property {Number} [sideMargins=100] Defines the percent of bar size to use as spacing between axes.
   * @property {Boolean} [useOrigin=true] Determines if {Tee.CustomBar#origin} value is used as bar minimum.
   * @property {Number} [origin=0] Defines the value to use as bar minimum.
   * @property {Number} [barSize=70] Defines the percent size of bars on available space.
   * @property {Number} [customBarSize=0] Defines the custom bar size in pixels. 
   * @property {Number} [offset=0] Defines the percent bar size to offset each bar.
   * @property {String} [barStyle="bar"] Which shape to draw ("bar", "ellipse", "line").
   * @property {Boolean} [stacked="no"] Use "no", "yes", "100", "sideAll", "self", "side" to define stack behaviour with other BarSeries.
   */
  Tee.CustomBar = function (o, o2) {
    Tee.Series.call(this, o, o2);

    this.sideMargins = 100;
    this.useOrigin = true;
    this.origin = 0;
    this.continuous = false;

    this.marks.visible = true;
    this.marks.location = "end";

    this.hover.enabled = true;

    this.offset = 0; // %
    this.barSize = 70; // %
	this.customBarSize=0;
    this.barStyle = "bar"; // "ellipse"
    var percent = 1;
    var f = this.format;
    f.fill = "";
    f.stroke.fill = "black";
    f.shadow.visible = true;
    f.round.x = 4;
    f.round.y = 4;
    f.gradient.visible = true;

    f.depth = 1;

    this.stacked = "no"; // "yes", "100", "sideAll", "self", "side"

    this.drawBar = function (r, barStyle) {
      var old = f.depth,
        oldz = f.z,
        ctx = this.chart.ctx;

      if (!barStyle) barStyle = this.barStyle;

      f.depth =
        (0.5 * Math.min(this.yMandatory ? r.width : r.height, 200)) / 100; // 200=Three.totalDepth !!

      if (r.width > 0 && r.height > 0) {
        if (this.stacked !== "side") f.z = f.z + 0.5 * (1 - f.depth);

        if (barStyle === "bar") {
          f.cube(r);
        } else if (barStyle === "line") {
          var pos;

          ctx.beginPath();
          ctx.z = f.z + f.depth * 0.5;

          if (this.yMandatory) {
            pos = r.x + 0.5 * r.width;

            ctx.moveTo(pos, r.y);
            ctx.lineTo(pos, r.y + r.height);

            //f.rectPath(pos,r.y,1,r.height);
          } else {
            pos = r.y + 0.5 * r.height;

            ctx.moveTo(r.x, pos);
            ctx.lineTo(r.x + r.width, pos);

            //f.rectPath(r.x,pos,r.width,1);
          }
        } else if (barStyle === "cylinder") f.cylinder(r, 1, this.yMandatory);
        else if (barStyle === "cone") f.cylinder(r, 0, this.yMandatory);
        else if (barStyle === "ellipsoid" && this.chart.__webgl) {
          ctx.depth = f.depth;
          ctx.z = f.z;
          ctx.image = this.image;
          ctx.ellipsoid(r, this.yMandatory);
        } else {
          f.z += 0.5 * f.depth;
          f.ellipsePath(
            this.chart.ctx,
            r.x + r.width * 0.5,
            r.y + r.height * 0.5,
            r.width,
            r.height
          );
        }

        f.depth = old;
        f.z = oldz;

        return true;
      } else return false;
    };

    /**
     * @returns {Number} Returns the number of visible Tee.CustomBar series that are displayed before this series.
     */
    this.countAll = function (upToThis) {
      var i = this.chart.series.items,
        res = 0,
        len = i.length,
        t,
        s;

      for (t = 0; t < len; t++) {
        s = i[t];

        if (s == this && upToThis) break;
        else if (s.visible && s.constructor == this.constructor)
          res += s.data.values.length;
      }

      return res;
    };

    var offset = new Point(),
      originPos,
      bar = new Rectangle(),
      visibleBar = { total: 0, index: 0 };

    this._margin = 0;

    this.calcBarOffset=function(axisSize) {

      var barSize=axisSize, all=(this.stacked=="sideAll");

      this.countall= all ? this.countAll(true) : 0;

      var tmpLen, len= all ? this.countAll() : this.data.values.length;

      if (len>1)
        barSize /= (len);

      if (this.stacked=="no") {
        barSize /= visibleBar.total;
        offset.x=
          ((this.customBarSize>0) ? this.customBarSize : (barSize*this.barSize*0.01)) *
          ((visibleBar.total==1) ? -0.5 : (visibleBar.index -(visibleBar.total*0.5))) ;
        tmpLen=visibleBar.total;
      }
      else {
        offset.x=-barSize*0.5;
        tmpLen=1;
      }

      offset.y=(this.customBarSize>0) ? this.customBarSize : barSize*this.barSize*0.01;

      this._margin=(0.5*tmpLen*offset.y)+this.sideMargins*(tmpLen*(barSize-offset.y))*0.005;

      if (this.stacked!="no")
        offset.x+=(this.offset*barSize*0.01) + (barSize-offset.y)*0.5;
    }

    this.calcStackPos = function (t, p) {
      var v, tmp, a;

      if (this.isStacked) {
        this.calcStack(t, p, this.data.values[t]);

        (v = this.pointOrigin(t, false)), (a = this.mandatoryAxis);

        if (this.isStack100) {
          tmp = this.pointOrigin(t, true);
          originPos = tmp === 0 ? a.endPos : a.calc((v * 100.0) / tmp);
        } else originPos = a.calc(v);
      } else {
        this.calc(t, p);

        if (this.stacked == "sideAll") {
          tmp = this.notmandatory.calc(this.countall + t);
          if (this.yMandatory) p.x = tmp;
          else p.y = tmp;
        }
      }
    };

    var hasPaintedOver = false;

    this.drawSortedValues = function (sorted) {
      var animation = this.sortedOptions.sortedDrawAnimation;
      if (
        this.sortedOptions.sorted != sorted &&
        !this.sortedOptions.sorting &&
        animation &&
        !animation.running
      ) {
        this.sortedOptions.sorted = sorted;
        var data = this.data;
        if (this.sortedOptions.sorted) this.sortValues();

        var prevValues = this.sortedOptions.sorted
          ? this.sortedOptions.originalValues.slice()
          : this.sortedOptions.sortedValues.slice();
        var values = this.data.values;
        var endValues = this.sortedOptions.sorted
          ? this.sortedOptions.sortedValues.slice()
          : this.sortedOptions.originalValues.slice();

        var series = this;
        var sortedOptions = this.sortedOptions;
        animation.chart = this.chart;
        if (sortedOptions.sortingAnimationType == "horizontalchange") {
          animation.doStep = function (f) {
            //do with draw
            if (f < 1) {
              percent = f;
              series.draw();
            }
          };

          animation.onstop = function () {
            percent = 1;
            if (sortedOptions.sorted)
              for (var i = 0; i < prevValues.length; i++) {
                values[i] = endValues[i];
              }
            //animation.running = false;
          };
          animation.onstart = function () {
            if (!sortedOptions.sorted)
              for (var i = 0; i < prevValues.length; i++) {
                values[i] = endValues[i];
              }
            //animation.running = true;
          };
        } else {
          animation.doStep = function (f) {
            //do with draw
            if (f < 1) {
              for (var i = 0; i < prevValues.length; i++) {
                values[i] = prevValues[i] + (endValues[i] - prevValues[i]) * f;
              }
            }
          };

          animation.onstop = function () {
            percent = 1;
            for (var i = 0; i < prevValues.length; i++) {
              values[i] = endValues[i];
            }
            animation.running = false;
          };
          animation.onstart = function () {
            animation.running = true;
          };
        }
        animation.animate();

        this.data.labels = this.sortedOptions.sorted
          ? this.sortedOptions.sortedLabels.slice()
          : this.sortedOptions.originalLabels.slice();

        this.chart.draw();
      }
    };
    this.draw = function () {
      var len = this.data.values.length;

      if (len > 0) {
        this.initOffsets();

        var p = new Point(),
          c = this.chart.ctx;

        if (!this.hover.enabled) {
          f.stroke.prepare();
          f.shadow.prepare(c);
        }

        var hover = this.hover.enabled,
          isLine = this.barStyle === "line",
          _styles = this.data.styles;

        if (hover && this.format.image.url != null) {
          this.hover.image.url = this.format.image.url;
          this.hover.image.repeat = this.format.image.repeat;
          this.hover.image.backFill = this.format.image.backFill;
        }

        f.z = 0; //B407 init
        for (var t = 0; t < len; t++)
          if (!this.isNull(t)) {
            this.calcStackPos(t, p);

            var sortedP = new Tee.Point();

            if (percent < 1) {
              this.calcStackPos(
                this.sortedOptions.sortedValuesIndices.findIndex(function (x) {
                  return x == t;
                }),
                sortedP
              );
              if (this.sortedOptions.sorted)
                p.x = (-p.x + sortedP.x) * percent + p.x;
              else p.x = (p.x - sortedP.x) * percent + sortedP.x;
            }

            if (this.onbeforedrawPoint) this.onbeforedrawPoint(t);

            this.calcBarBounds(
              p,
              bar,
              offset,
              originPos instanceof Array ? originPos[t] : originPos
            );

            this.barRectangle = bar;
            var pointPainted = this.drawBar(bar, _styles ? _styles[t] : null);

            var isover = hover && this.over == t,
              ff = isover ? this.hover : f;

            c.fillStyle = this.getFillStyle(
              bar,
              this.getFill(t, ff.fill === "" ? f : ff)
            );

            if (!this.format.gradient.visible) ff.fill = c.fillStyle;

            if (hover) ff.shadow.prepare(c);

            if (isover) {
              if (this.format.image.url != null && !hasPaintedOver) {
                //prevent fill flicker for first hover
                hasPaintedOver = true;
                f.draw(c, null, bar);
              } else c.fill();
            }

            if (pointPainted) {
              ff.draw(c, null, bar);

              if (isLine || ff.stroke.fill !== "") {
                if (hover) ff.stroke.prepare();

                if (!isover && isLine) c.strokeStyle = c.fillStyle;

                c.shadowColor = "transparent";

                c.stroke();

                if (ff.shadow.visible) c.shadowColor = ff.shadow.color;
              }
            }
          }
      }
    };

    this.calcColorEach = function () {
      this.chart.series.visibleCount(this, Tee.CustomBar, visibleBar);
      this.isColorEach =
        this.colorEach == "yes" ||
        (this.colorEach == "auto" && visibleBar.total == 1);
    };

    this.initOffsets = function () {
      var nomand = this.notmandatory,
        mand = this.mandatoryAxis,
        range = this.yMandatory
          ? this.maxXValue() - this.minXValue()
          : this.maxYValue() - this.minYValue();

      if (this.stacked == "sideAll") this.calcBarOffset(nomand.axisSize);
      else
        this.calcBarOffset(
          range === 0 ? nomand.axisSize : nomand.calcSize(range)
        );

      if (this.useOrigin) {
        if (this.origin instanceof Array) {
          originPos = [];
          for (var t = 0; t < this.origin.length; t++) {
            originPos[t] = mand.calc(this.origin[t]);
          }
        } else {
          originPos = mand.calc(this.origin);
        }
      } else if (this.yMandatory)
        originPos = mand.inverted ? mand.startPos : mand.endPos;
      else originPos = mand.inverted ? mand.endPos : mand.startPos;

      var st = this.stacked;

      this.isStacked = st !== "no" && st !== "sideAll" && st !== "side";
      this.isStack100 = st === "100";
    };

    /**
     * @returns {Number} Returns the index of series bar that contains {@link Tee.Point} p parameter.
     */
    this.clicked = function (p) {
      this.initOffsets();

      var p2 = new Point(),
        len = this.data.values.length,
        t;

      for (t = 0; t < len; t++)
        if (!this.isNull(t)) {
          this.calcStackPos(t, p2);
          this.calcBarBounds(
            p2,
            bar,
            offset,
            originPos instanceof Array ? originPos[t] : originPos
          );

          if (bar.contains(p)) return t;
        }

      return -1;
    };

    this.markPos = function (t, p) {
      var yMand = this.yMandatory,
        op = offset.x + offset.y * 0.5,
        m = this.marks;

      this.calcStackPos(t, p);

      if (this.stacked == "sideAll") {
        var tmp = this.notmandatory.calc(this.countall + t);
        yMand ? (p.x = tmp) : (p.y = tmp);
      }

      var inv =
        this.useOrigin &&
        this.data.values[t] <
          (this.origin instanceof Array ? this.origin[t] : this.origin);
      if (this.mandatoryAxis.inverted) inv = !inv;

      // Marks location

      if (m.location == "center") {
        this.calcBarBounds(
          p,
          bar,
          offset,
          originPos instanceof Array ? originPos[t] : originPos
        );

        if (m.canDraw(p.x, p.y, t, inv)) {
          if (yMand) p.y = bar.y + bar.height * 0.5 + m.bounds.height * 0.5;
          else p.x = bar.x + bar.width * 0.5 - m.bounds.width * 0.5;
        }
      }

      var a = this.chart.aspect,
        is3d = a.view3d,
        wx = 0,
        wy = 0;

      if (is3d && a.orthogonal) {
        wx = a._orthox * 0.5;
        wy = a._orthoy * 0.5;
      }

      if (yMand) {
        p.x += is3d ? op + wx : op;
        if (is3d) p.y -= wy;
      } else {
        p.y += is3d ? op - wy : op;
        if (is3d) p.x += wx;
      }

      return inv;
    };

    /*
  this.drawMarks=function() {
    var len=this.data.values.length, m=this.marks, p=new Point(), inv;

    if (len>0)
      for(var t=0; t<len; t+=m.drawEvery)
        if (!this.isNull(t)) {
          inv=this.markPos(t,p);
          m.drawMark(p.x, p.y, t, inv);
        }
  }
  */

    this.labelOrTitle = function (index) {
      var s = this.title,
        l = this.data.labels[index];
      return visibleBar.total > 1 ? s || l : this.parent.labelOrTitle(index);
    };

    this.initZ = function (index, total) {
      var s,
        f = this.format;

      if (this.stacked !== "side") {
        f.z = 0;
        f.depth = 1;

        while (index > 1) {
          index--;
          s = this.chart.series.items[index];

          if (s.visible && s.constructor == this.constructor) {
            f.z = s.z;
            f.depth = s.depth;
            break;
          }
        }
      } else Tee.Series.prototype.initZ.call(this, index, total);

      this.marks.format.z = f.z + f.depth * 0.5;
    };
  };

  Tee.CustomBar.prototype = new Tee.Series();
  Tee.CustomBar.prototype.parent = Tee.Series.prototype;
  Tee.CustomBar.constructor = Tee.CustomBar;

  /**
   * @constructor
   * @augments Tee.CustomBar
   * @class Draws data as vertical bars
   */
  Tee.Bar = function (o, o2) {
    Tee.CustomBar.call(this, o, o2);

    this.calc = function (index, p) {
      this.isStacked
        ? this.calcStack(index, p, this.data.values[index])
        : this.parent.calc.call(this, index, p);
    };

    this.horizMargins = function (p) {
      this.initOffsets();
      p.x = this._margin;
      p.y = this._margin;
    };

    this.vertMargins = function (p) {
      var m = this.marks,
        st = this.format.stroke,
        hasNeg =
          this.minYValue() <
          (this.origin instanceof Array ? ArrayMin(this.origin) : this.origin);

      if (m.visible && m.location !== "center") {
        p.y =
          m.arrow.length +
          m.format.textHeight("Wj") +
          m.margins.top +
          m.margins.bottom;
        st = m.format.stroke;
      }

      if (st.fill !== "") p.y += 2 * st.size + 1;

      if (hasNeg) p.x = p.y;
    };

    this.maxXValue = function () {
      return this.stacked === "sideAll"
        ? this.countAll() - 1
        : this.parent.maxXValue.call(this);
    };

    this.minYValue = function () {
      var res = this.parent.minYValue.call(this);
      return this.useOrigin
        ? Math.min(
            this.origin instanceof Array ? ArrayMin(this.origin) : this.origin,
            res
          )
        : res;
    };

    this.maxYValue = function () {
      if (this.stacked === "sideAll" || this.stacked === "side") {
        var res = 0,
          s,
          ss = this.chart.series.items,
          l = ss.length,
          t,
          val;

        for (t = 0; t < l; t++) {
          s = ss[t];
          if (s.visible && s.constructor === this.constructor) {
            val = s.parent.maxYValue.call(s);
            if (val > res) res = val;
          }
        }

        return res;
      } else return this.stackMaxValue();
    };

    this.calcBarBounds = function (p, bar, offset, originPos) {
      bar.x = p.x + offset.x;
      bar.width = offset.y;

      if (this._vertAxis.inverted) {
        bar.y = originPos;
        bar.height = p.y - bar.y;
      } else {
        bar.y = p.y;
        bar.height = originPos - p.y;
      }

      if (bar.height < 0) {
        bar.y += bar.height;
        bar.height = -bar.height;
      }
    };
  };

  Tee.Bar.prototype = new Tee.CustomBar();
  Tee.Bar.prototype.parent = Tee.CustomBar.prototype;

  /**
   * @constructor
   * @augments Tee.CustomBar
   * @class Draws data as horizontal bars
   */
  Tee.HorizBar = function (o, o2) {
    Tee.CustomBar.call(this, o, o2);

    this.yMandatory = false;
    this.format.gradient.direction = "rightleft";

    /**
     * @returns {Number} Returns the maximum width in pixels of series marks texts.
     */
    this.maxMarkWidth = function () {
      var res = 0,
        f,
        t,
        m = this.marks,
        l = this.count(),
        n;

      if (m.visible) {
        f = this.marks.format;
        f.font.prepare();

        for (t = 0; t < l; t += m.drawEvery)
          if (!this.isNull(t)) {
            n = f.textWidth(this.markText(t) + "W");
            if (n > res) res = n;
          }
      }
      return res;
    };

    this.horizMargins = function (p) {
      var m = this.marks,
        st = this.format.stroke,
        hasNeg = this.minXValue() < this.origin;

      if (m.visible && m.location !== "center") {
        p.y =
          m.arrow.length +
          this.maxMarkWidth() +
          m.margins.left +
          m.margins.right;
        st = m.format.stroke;
      }

      if (st.fill !== "") p.y += 2 * st.size + 1;

      if (hasNeg) p.x = p.y;
    };

    this.vertMargins = function (p) {
      this.initOffsets();
      p.x += this._margin;
      p.y += this._margin;
    };

    this.maxYValue = function () {
      return this.stacked == "sideAll"
        ? this.countAll() - 1
        : this.parent.maxXValue.call(this);
    };

    this.minYValue = function () {
      return this.stacked == "sideAll" ? 0 : this.parent.minXValue.call(this);
    };

    this.minXValue = function () {
      var res = this.parent.minYValue.call(this);
      return this.useOrigin ? Math.min(this.origin, res) : res;
    };

    this.maxXValue = function () {
      return this.stackMaxValue();
    };

    this.calcBarBounds = function (p, bar, offset, originPos) {
      bar.y = p.y + offset.x;
      bar.height = offset.y;

      if (this._horizAxis.inverted) {
        bar.x = p.x;
        bar.width = originPos - p.x;
      } else {
        bar.x = originPos;
        bar.width = p.x - bar.x;
      }

      if (bar.width < 0) {
        bar.x += bar.width;
        bar.width = -bar.width;
      }
    };
  };

  Tee.HorizBar.prototype = new Tee.CustomBar();
  Tee.HorizBar.prototype.parent = Tee.CustomBar.prototype;

  /**
   * @constructor
   * @augments Tee.HorizBar
   * @class Draws a HorizBar with states in background and one limit.
   * @property {object}[limit] limit contains the properties of the limit.
   * @property {object}[states] states contains the properties of the states.
   */
  Tee.Bullet = function (o, o2) {
    Tee.HorizBar.call(this, [o[0]], o2 ? [o2[0]] : []);

    this.barSize = 25;
    /**
     * @property {number}[origin: 38] it contains the origin of the limit.
     * @property {number}[width: 0.2] it contains the width of the limit.
     * @property {number}[height: 35] it contains the height of the limit.
     * @property {string}[color: "red"] it contains the color of the limit.
     * @property {object}[bar: null] it contains the limit bar.
     */
    this.limit = {
      origin: 38,
      width: 0.2,
      height: 35,
      color: "red",
      bar: null,
    };
    this.marks.visible = false;
    /**
     * @property {array}[colors: ["#111", "#444", "#777", "#BBB", "#EEE"]] it contains the colors of the states.
     * @property {array}[values: [10, 10, 10, 10, 10]] it contains the values of the states.
     * @property {array}[barStates: []] it contains the bar array of the states.
     * @property {number}[barSize: 50] it contains the size of the states.
     * @property {boolean}[gradientVisible: false] it contains if the gradient will be visible or not.
     */
    this.states = {
      colors: ["#111", "#444", "#777", "#BBB", "#EEE"],
      values: [10, 10, 10, 10, 10],
      barStates: [],
      barSize: 50,
      gradientVisible: false,
    };
    createStatesBars(this.states, this.origin);
    function createStatesBars(states, origin) {
      var sumStatesValues = 0;
      if (states.values.length > 0) {
        states.barStates.push(
          createBarState(
            states.values[0],
            sumStatesValues + origin,
            states.barSize,
            states.colors[0],
            states.colors[0],
            states.gradientVisible
          )
        );
        sumStatesValues += states.values[0];
        for (var i = 1; i < states.values.length; i++) {
          states.barStates.push(
            createBarState(
              states.values[i],
              sumStatesValues + origin,
              states.barSize,
              states.colors[(i - 1) % states.colors.length],
              states.colors[i % states.values.length],
              states.gradientVisible
            )
          );
          sumStatesValues += states.values[i];
        }
      }
    }

    this.minValue = function () {
      if (this.states.barStates.length >= 1)
        return this.states.barStates[0].origin;
      else return 0;
    };
    this.maxValue = function () {
      if (this.states.barStates.length >= 1)
        return this.states.barStates[this.states.barStates.length - 1].data
          .values[0];
      else return 0;
    };
    function createBarState(
      value,
      origin,
      barSize,
      color1,
      color2,
      gradientVis
    ) {
      var values = [value + origin];
      var bar = new Tee.HorizBar(values);
      bar.stacked = "side";
      bar.origin = origin;
      bar.barSize = barSize;
      bar.marks.visible = false;
      bar.format.round.x = 0;
      bar.format.round.y = 0;
      bar.format.gradient.colors = [color1, color2];
      if (gradientVis) {
        bar.format.gradient.direction = "leftright";
        bar.format.gradient.visible = true;
      } else {
        bar.format.gradient.visible = false;
        bar.format.fill = color2;
        bar.palette.colors = [color2];
      }
      bar.format.shadow.visible = false;
      bar.format.stroke.fill = "rgba(0,0,0,0.0)";

      return bar;
    }
    this.parentDraw = this.draw;
    this.draw = function () {
      var minVal, maxVal;
      minVal = this.minValue();
      maxVal = this.maxValue();
      this.chart.zoom.reset = function () {
        this.chart.axes.each(function () {
          this.automatic = true;
        });
        this.chart.axes.bottom.setMinMax(minVal, maxVal);
      };
      this.states.barStates = [];
      createStatesBars(this.states, this.origin);
      for (var i = 0; i < this.states.barStates.length; i++) {
        if (this.chart)
          this.states.barStates[i].setChart(
            this.states.barStates[i],
            this.chart
          );
        this.states.barStates[i].draw();
      }
      this.limit.bar = createBarState(
        this.limit.width,
        this.limit.origin + this.origin,
        this.limit.height,
        this.limit.color,
        this.limit.color,
        false
      );
      this.limit.bar.setChart(this.limit.bar, this.chart);
      this.limit.bar.draw();
      this.parentDraw();
    };
  };
  Tee.Bullet.prototype = new Tee.HorizBar();
  Tee.Bullet.prototype.parent = Tee.HorizBar.prototype;
  /**
   * @constructor
   * @augments Tee.Series
   * @class Base abstract class for line, area, scatter plots
   * @property {Tee.CustomSeries-Pointer} pointer Paints a visual representation at each point position.
   * @property {String} stacked Defines if multiple series are displayed one on top of each other.
   * @property {Number} smooth Draws lines between points as diagonals (value 0) or smooth curves (value > 0 < 1).
   * @property {Boolean} [stairs=false] Draws lines between points in stairs mode instead of diagonals.
   * @property {Boolean} [invertedStairs=false] Draws lines between points in inverted stairs mode instead of diagonals.
   */
  Tee.CustomSeries = function (o, o2) {
    Tee.Series.call(this, o, o2);

    this.stacked = "no"; // "yes", "100"
    this.stairs = false;
    this.invertedStairs = false;

    this.continuous = true; //allows tooltip interpolation between points
	this.clickTolerance = 3; //distance for click sensitivity 

    this.hover.enabled = true;
    this.hover.line = false;

    /*
     * @private
     */
    this.isStacked = false;
    this.isStack100 = false;

    this.smooth = 0;

    /**
     * @constructor
     * @public
     * @class Formatting properties to draw symbols at series data positions
     * @property {Number} [width=12] The horizontal size in pixels
     * @property {Number} [height=12] The vertical size in pixels
     * @property {Tee.Format} format Visual properties to paint pointers.
     * @property {Boolean} [colorEach=false] Determines if pointers will be filled using a different color
     * for each point in series.
     * @property {String} [style="rectangle"] The shape to draw at pointer positions
     */
    function Pointer(chart, series) {
      /*
       * @private
       */
      this.setChart = function (chart) {
        this.chart = chart;
        this.format.setChart(chart);
      };

      this.chart = chart;

      this.inflateMargins = true;

      /*
       * Visual properties to paint pointers
       */
      var f = (this.format = new Tee.Format(chart));

      f.shadow.visible = false;
      f.fill = "";
      f.gradient.colors = ["white", "white", "white"];
      f.gradient.visible = true;
      f.shadow.visible = true;

      /*
       * Determines if pointers will be displayed.
       */
      this.visible = false;

      this.colorEach = false;

      /*
       * Visual style of pointer ("rectangle", "cylinder", "cone", "ellipse", "sphere", "triangle", "diamond", "downtriangle", "cross", "x").
       */
      this.style = "rectangle";

      this.width = 12;
      this.height = 12;

      this.draw = function (p, index, f, fill) {
        var c = this.chart.ctx;

        f.z = series.format.z;

        if (this.transform) {
          c.save();
          this.transform(p.x, p.y, index);
        }

        var w = this.width * 0.5,
          h = this.height * 0.5,
          r;

        if (this.style == "cube") {
          r = {
            x: p.x - w,
            y: p.y - h,
            width: this.width,
            height: this.height,
          };

          var wh = Math.max(w, h) / 50; // 50=totalDepth !

          f.z = (series.format.z + series.format.depth) * 0.5 - wh * 0.5;
          f.depth = wh;

          f.cube(r);
          f.draw(c, null, r);
        } else if (this.style == "rectangle")
          f.rectangle(p.x - w, p.y - h, this.width, this.height);
        else if (this.style == "ellipse")
          f.ellipse(p.x, p.y, this.width, this.height);
        else if (this.style == "sphere") {
          f.depth = series.format.depth;
          f.sphere(p.x, p.y, this.width, this.height);
        } else if (this.style == "cylinder") {
          r = {
            x: p.x - w,
            y: p.y - h,
            width: this.width,
            height: this.height,
          };

          // Remember gradient properties:
          var g = f.gradient,
            oldDir = g.direction,
            oldColors = g.colors.slice(0);

          // Set gradient to resemble cylinder:
          g.direction = "leftright";
          g.colors = [g.colors[1], g.colors[0], g.colors[1]];

          f.cylinder(r, 1, true);
          f.draw(c, null, r);

          // Restore gradient properties:
          g.direction = oldDir;
          g.colors = oldColors;
        } else if (this.style == "cone") {
          r = {
            x: p.x - w,
            y: p.y - h,
            width: this.width,
            height: this.height,
          };

          // Draw cylinder with top radius 0% to create a cone:
          f.cylinder(r, 0, true);

          f.draw(c, null, r);
        } else if (this.style == "triangle")
          f.polygon([
            new Point(p.x, p.y - h),
            new Point(p.x - w, p.y + h),
            new Point(p.x + w, p.y + h),
          ]);
        else if (this.style == "downtriangle")
          f.polygon([
            new Point(p.x, p.y + h),
            new Point(p.x - w, p.y - h),
            new Point(p.x + w, p.y - h),
          ]);
        else if (this.style == "diamond")
          f.polygon([
            new Point(p.x, p.y - h),
            new Point(p.x - w, p.y),
            new Point(p.x, p.y + h),
            new Point(p.x + w, p.y),
          ]);
        else {
          c.beginPath();

          if (this.style == "cross") {
            c.moveTo(p.x - w, p.y);
            c.lineTo(p.x + w, p.y);
            c.moveTo(p.x, p.y - h);
            c.lineTo(p.x, p.y + h);
          }
          if (this.style == "x") {
            c.moveTo(p.x - w, p.y - h);
            c.lineTo(p.x + w, p.y + h);
            c.moveTo(p.x - w, p.y + h);
            c.lineTo(p.x + w, p.y - h);
          }

          f.stroke.prepare(fill);
          c.stroke();
        }

        if (this.transform) c.restore();
      };

      this.setSize = function (size) {
        this.width = size;
        this.height = size;
      };
    }

    /*
     * Visual indication at series point positions.
     */
    this.pointer = new Pointer(this.chart, this);

    this.maxYValue = function () {
      return this.stackMaxValue();
    };

    this.calc = function (index, p) {
      this.isStacked
        ? this.calcStack(index, p, this.data.values[index])
        : Tee.Series.prototype.calc.call(this, index, p);
    };

    this.calcColorEach = function () {
      this.isColorEach = this.colorEach == "yes" || this.pointer.colorEach || this.colorEachLine == "yes" || this.colorEachLine == true ;
    };

    this.initZ = function (index, total) {
      var s,
        f = this.format;

      if (this.stacked !== "no") {
        f.z = 0;
        f.depth = 1;

        while (index > 1) {
          index--;
          s = this.chart.series.items[index];

          if (s.visible && s.constructor == this.constructor) {
            f.z = s.z;
            f.depth = s.depth;
            break;
          }
        }
      } else Tee.Series.prototype.initZ.call(this, index, total);

      this.marks.z = f.z + f.depth * 0.5;
    };
  };

  Tee.CustomSeries.prototype = new Tee.Series();

  Tee.CustomSeries.prototype.drawPointers = function () {
    var len = this.data.values.length,
      f = this.pointer.format,
      isEach = this.colorEach == "yes" || this.pointer.colorEach;

    if (!isEach) if (f.fill === "") f.fill = this.format.fill;

    var p = new Point(),
      g = f.gradient,
      t,
      fill = f.fill,
      old = fill;

    if (!isEach && g.visible) g.setEndColor(fill);

    for (t = 0; t < len; t++)
      if (!this.isNull(t)) {
        this.calc(t, p);

        fill = this.getFill(t, f);

        if (fill != old) {
          g.visible ? g.setEndColor(fill) : (f.fill = fill);
          old = fill;
        }

        this.getSize(t);

        if (this.onbeforedrawpointer) this.onbeforedrawpointer(t, this.pointer);

        this.pointer.draw(p, t, f, fill);

        if (this.hover.enabled && this.over == t)
          this.pointer.draw(p, t, this.hover, fill);
      }

    if (isEach) f.fill = old;
  };

  /**
   * @private
   */
  Tee.CustomSeries.prototype.setChart = function (series, chart) {
    var tmp = Tee.Series.prototype.setChart;
    tmp(series, chart);
    series.pointer.setChart(chart);
  };

  /**
   * @returns {Number} Returns the index of the series point that contains p {@link Tee.Point} parameter.
   */
  Tee.CustomSeries.prototype.clicked = function (p) {
    var p1 = new Point(),
      p2 = new Point(),
      len = this.data.values.length,
      t;

    // Line+Pointer only acts when this.hover.line=true

    if (
      this.drawLine &&
      len > 0 &&
      (this.hover.line || !this.pointer.visible)
    ) {
      this.calc(0, p1);

      for (
        t = 1;
        t < len;
        t++ // if (!this.isNull(t))  <--- pending
      ) {
        this.calc(t, p2);

        if (this.stairs) {
          var p1a;

          if (this.invertedStairs) p1a = new Point(p2.x, p1.y);
          else p1a = new Point(p1.x, p2.y);

          if (pointInLine(p, p1, p1a, this.clickTolerance) || pointInLine(p, p1a, p2, this.clickTolerance))
            return t;
        } else if (pointInLine(p, p1, p2, this.clickTolerance))
          // near-line tolerance in pixels
          return t;

        p1.x = p2.x;
        p1.y = p2.y;
      }
    }

    if (this.pointer.visible) {
      var r = new Rectangle(),
        po = this.pointer;

      for (t = len - 1; t >= 0; t--)
        if (!this.isNull(t)) {
          this.calc(t, r);
          this.getSize(t);
          r.x -= po.width * 0.5;
          r.width = po.width;
          r.y -= po.height * 0.5;
          r.height = po.height;

          if (r.contains(p)) return t;
        }
    }

    return -1;
  };

  Tee.CustomSeries.prototype.horizMargins = function (p) {
    var po = this.pointer,
      s = po.format.stroke;
    if (po.visible && po.inflateMargins)
      p.x = p.y = (s.fill !== "" ? s.size : 0) + 1 + po.width * 0.5;
  };

  Tee.CustomSeries.prototype.vertMargins = function (p) {
    var po = this.pointer,
      s = po.format.stroke;
    if (po.visible && po.inflateMargins)
      p.x = p.y = (s.fill !== "" ? s.size : 0) + 1 + po.height * 0.5;
  };

  Tee.CustomSeries.prototype.getSize = function () {};

  /**
   * @constructor
   * @augments Tee.CustomSeries
   * @class Draws series data as a contiguous polyline between points
   * @property {Boolean} [stairs=false] Determines if lines between points are direct (diagonals) or as stairs (horizontal and vertical).
   */
  Tee.Line = function (o, o2) {
    Tee.CustomSeries.call(this, o, o2);

    this.drawLine = true;

    this.treatNulls = "dontPaint";

    this.colorEachLine = "no";
	
    var f = this.format;
    f.shadow.visible = true;
    f.shadow.blur = 10;
    f.lineCap = "round";
	
    this.doDrawLine = function (c) {
      var p = new Point(),
        oldX,
        oldY,
        len = this.data.values.length,
        t,
        smop,
        s,
        begin = 0,
        end = len,
        no = this.notmandatory;

      if (!this.smooth && !this.data.x) {
        begin = Math.max(0, trunc(no.minimum) - 1);
        end = Math.min(len, trunc(no.maximum) + 2);
      }

      var a = this.chart.aspect,
        is3d = a.view3d;

	  // ***** mods #2609 start here ******		
	  var isEach = ((this.colorEachLine == "yes") || (this.colorEachLine == true));
	  
	  f.fill = this.getFill(0, f);
  
      if (!isEach) if (f.fill === "") f.fill = this.format.fill;

      c.beginPath(); //clear c

      if (this.smooth > 0 && typeof Tee.drawSpline !== "undefined") {
        smop = new Array(2 * len);

        for (t = 0; t < len; t++) {
          this.calc(t, p);

          smop[2 * t] = p.x;
          smop[2 * t + 1] = p.y;
        }

        if (c.spline) c.spline(smop);
        else Tee.drawSpline(c, smop, this.smooth, true);
      } else {
        var noNulls = this.treatNulls !== "skip";
	
        var g = f.gradient;
		var fill = f.stroke.fill;
		var old = fill;
		
		if (!isEach && g.visible) g.setEndColor(f.stroke.fill);
		
	    var stSeg = f.stroke;
		stSeg.prepare(stSeg.fill);

        for (t = begin; t < end; t++)
          if (this.isNull(t)) {
            if (noNulls) begin = -1; // Dont Paint until next non-null
          } else 
		  {
            this.calc(t, p);

			if (isEach)
			{
				if ((isEach) && (t != begin))
				  c.beginPath();
			
				fill = this.getFill(t, f);

				if (fill != old) {
				  g.visible ? g.setEndColor(fill) : (f.fill = fill);
				  old = fill;
				}

				if (t == begin || begin === -1) {
				  c.moveTo(p.x, p.y);
				  begin = 0;
				} else if (this.stairs) {
				  if (this.invertedStairs){
					  c.moveTo(oldX, oldY);
					  c.lineTo(p.x, oldY);
					  c.lineTo(p.x, p.y);
				  }
				  else {
					  c.moveTo(oldX, oldY);
					  c.lineTo(oldX, p.y);
					  c.lineTo(p.x, p.y);
				  }
				} else {
					c.moveTo(oldX, oldY);
					c.lineTo(p.x, p.y);
				}

				c.strokeStyle = fill;
				c.stroke();
				
			}
			else {
				if (t == begin || begin === -1) {
				  c.moveTo(p.x, p.y);
				  begin = 0;
				} else if (this.stairs) {
				  if (this.invertedStairs) c.lineTo(p.x, oldY);
				  else c.lineTo(oldX, p.y);

				  c.lineTo(p.x, p.y);
				} else c.lineTo(p.x, p.y);
			}
			
			oldX = p.x;
			oldY = p.y;
			
			if ((isEach) && (t != begin))
			  c.closePath();
          }
      }

      var st = f.stroke;

      // Chrome bug with shadow and stroke size == 1
      if (this.chart.isChrome && f.shadow.visible)
        st.size = Math.max(1.1, st.size);

      c.z = f.z;
      c.depth = f.depth;

      s = st.fill;
      if (s === "") s = f.fill;

      st.prepare(s);
      f.shadow.prepare(c);

      if (is3d) {
        c.fillStyle = f.fill;
        c.fill();
      }

      if (!isEach)
        if (s !== "") c.stroke();
    };

    this.draw = function () {
      var len = this.data.values.length;

      if (len > 0) {
        this.isStacked = this.stacked != "no";
        this.isStack100 = this.stacked == "100";

        if (this.drawLine) this.doDrawLine(this.chart.ctx);

        if (this.pointer.visible) this.drawPointers();
      }
    };
  };

  Tee.Line.prototype = new Tee.CustomSeries();

  /**
   * @constructor
   * @augments Tee.Line
   * @class Draws series data as points at vertical and horizontal axes positions
   */
  Tee.PointXY = function (o, o2) {
    Tee.Line.call(this, o, o2);
    this.hover.enabled = true;
    this.pointer.visible = true;
    this.drawLine = false;
  };

  Tee.PointXY.prototype = new Tee.Line();

  Tee.Series.prototype.cellRect = function (r, act, series) {
    var visible = { total: 0, index: -1 };
    r.setFrom(this.chart.chartRect);
    this.chart.series.visibleCount(this, series, visible);

    if (act && visible.total > 1) {
      var cols = Math.round(Math.sqrt(visible.total)),
        rows = Math.round(visible.total / cols);

      if (r.width > r.height) {
        var tmp = cols;
        cols = rows;
        rows = tmp;
      }

      r.width /= cols;
      r.x += 1.03 * (visible.index % cols) * r.width;

      r.height /= rows;
      r.y += 1.03 * trunc(visible.index / cols) * r.height;

      // % spacing between multiple series
      r.width *= 0.94;
      r.height *= 0.94;
    }

    return r;
  };

  /**
   * @constructor
   * @augments Tee.Series
   * @class Draws series data as slices of a circle
   * @property {Number} [rotation=0] Rotates all slices by specified degree from 0 to 360.
   * @property {Number[]} explode Determines percent of separation from each slice to pie center.
   * @property {Boolean} [clockwise = true] Direction of Values.
   */
  Tee.Pie = function (o, o2) {
    Tee.Series.call(this, o, o2);

    this.marks.style = "percent";

    this.donut = 0;
    this.rotation = 0;
    this.colorEach = "yes";
    this.useAxes = false;
    this.continuous = false;
    this.angleWidth = 360;
    this.maxRadius = 100;
    var f = this.format;
    f.stroke.fill = "black";
    f.shadow.visible = true;
    f.gradient.visible = true;
    f.gradient.direction = "radial";
    f.gradient.colors = ["white", "white", "white"];

    this.hover.enabled = true;

    this.sort = "values";
    this.orderAscending = false;

    this.explode = null;

    this.marks.visible = true;

    this.concentric = false;
    this.clockwise = true;

    /**
     * @returns {Number} Returns the index'th pie slice value.
     */
    this.getValue = function (index) {
      return this.data.values[index];
    };

    this.calcCenter = function (t, radius, mid, center) {
      if (this.explode) {
        var v = this.explode[t];
        if (v) {
          v = radius * v * 0.01;
          center.x += v * Math.cos(mid);
          center.y += v * Math.sin(mid);
        }
      }
    };

    this.clicked = function (p) {
      var c = this.chart.ctx,
        len = this.data.values.length,
        t,
        index;

      //IE8 ExCanvas does not support "isPointInPath"

      if (c.isPointInPath) {
        endAngle = angle = (Math.PI * this.rotation) / 180.0;

        for (t = 0; t < len; t++) {
          index = sorted ? sorted[t] : t;

          if (!this.isNull(index)) {
            this.slice(c, index);

            if (c.isPointInPath(p.x, p.y)) return index;
          }
        }
      }

      return -1;
    };

    var total,
      piex,
      piey,
      radius,
      donutRadius,
      center = { x: 0, y: 0 },
      sorted,
      angle,
      endAngle,
      hoverang;

    function calcPos(angle, p) {
      p.x = center.x + Math.cos(angle) * donutRadius;
      p.y = center.y + Math.sin(angle) * donutRadius;
    }

    // Return Pie radius, and returns pie center xy at "c" parameter
    this.getCenter = function (c) {
      c.x = center.x;
      c.y = center.y;
      return radius;
    };

    this.slice = function (c, index) {
      var p = new Point();

      var a =
        (Math.PI * 2 * (Math.abs(this.data.values[index]) / total)) /
        (360 / this.angleWidth);
      endAngle += this.clockwise ? a : -a;
      center.x = piex;
      center.y = piey;
      this.calcCenter(index, radius, (angle + endAngle) * 0.5, center);

      if (this.donut === 0) {
        p.x = center.x;
        p.y = center.y;
      } else calcPos(angle, p);

      var webgl = this.chart.__webgl;

      if (webgl) {
        calcPos(2 * Math.PI - angle, p);
        c.slice(
          p,
          center,
          radius,
          angle,
          endAngle,
          donutRadius,
          f.tube,
          f.beveled
        );
      } else {
        c.beginPath();
        c.moveTo(p.x, p.y);
        c.arc(center.x, center.y, radius, angle, endAngle, !this.clockwise);

        if (this.donut !== 0) {
          calcPos(endAngle, p);
          c.lineTo(p.x, p.y);
          c.arc(
            center.x,
            center.y,
            donutRadius,
            endAngle,
            angle,
            this.clockwise
          );
        }

        c.closePath();
      }

      if (index == this.over) hoverang = angle;

      angle = endAngle;
    };

    this.fill = function (i) {
      return this.getFillStyle(
        new Tee.Rectangle(
          center.x - radius,
          center.y - radius,
          radius * 2,
          radius * 2
        ),
        this.getFill(i)
      );
    };

    this.slices = function (shadow) {
      var c = this.chart.ctx,
        len = this.data.values.length,
        t,
        index;

      endAngle = angle = (Math.PI * this.rotation) / 180.0;

      // TODO: Replace with overriden Tee.Format.slice
      c.z = 0.5;
      c.depth = 1;

      for (t = 0; t < len; t++) {
        index = sorted ? sorted[t] : t;

        if (this.onbeforedrawPoint) this.onbeforedrawPoint(index);

        if (!this.isNull(index)) {
          this.slice(c, index);

          if (shadow) f.shadow.prepare(c);
          else c.fillStyle = this.fill(index);

          c.fill();

          if (!shadow) {
            var st = f.stroke;

            if (st.fill !== "") {
              st.prepare();
              c.stroke();
            }
          }
        }
      }
    };

    var r = new Rectangle();

    this.draw = function () {
      var len = this.data.values.length;

      if (len > 0) {
        var h = 0,
          m = this.marks;

        if (f.shadow.visible) h += 2 * f.shadow.height;

        if (m.visible) {
          m.format.font.prepare();
          h += m.format.textHeight("Wj") + m.arrow.length * 0.5;
        }

        this.cellRect(r, !this.concentric, Tee.Pie);

        piex = r.x + r.width * 0.5;
        piey = r.y + r.height * 0.5;

        radius = r.width * 0.5;
        var r2 = (r.height - 2 * h) * 0.5;

        if (r2 < 0) r2 = 0;
        if (r2 < radius) radius = r2;

        donutRadius = radius * this.donut * 0.01;
        radius = radius / (100 / this.maxRadius);
        total = ArraySumAbs(this.data.values);

        sorted = this.doSort(this.sort, this.orderAscending);

        if (!this.chart.__webgl) this.slices(true);

        this.slices(false);

        if (this.hover.enabled && this.over != -1) {
          var st = this.hover;
          if (st.stroke.fill !== "") {
            endAngle = angle = hoverang;

            var c = this.chart.ctx;
            this.slice(c, this.over);
            c.fillStyle = this.fill(this.over);
            st.draw(c, null, r);
          }
        }
      }
    };

    this.drawMarks = function () {
      var endAngle = (Math.PI * this.rotation) / 180.0,
        angle = endAngle,
        mid,
        v = this.data.values,
        len = v.length,
        index,
        t,
        a;

      this.marks.format.z = 0.5;

      for (t = 0; t < len; t += this.marks.drawEvery) {
        index = sorted ? sorted[t] : t;

        if (!this.isNull(index)) {
          a = Math.PI * 2 * (Math.abs(v[index]) / total);
          endAngle += this.clockwise ? a : -a;
          mid = (angle + endAngle) * 0.5;
          center.x = piex;
          center.y = piey;
          this.calcCenter(t, radius, mid, center);
          this.marks.drawPolar(center, radius, mid, index);
          angle = endAngle;
        }
      }
    };
  };

  Tee.Pie.prototype = new Tee.Series();

  /**
   * @constructor
   * @augments Tee.CustomSeries
   * @class Draws series data as filled mountain segments between points
   * @property {Boolean} [useOrigin=false] Determines if {Tee.Area#origin} value is used as area minimum.
   * @property {Number} [origin=0] Defines the value to use as area minimum.
   */
  Tee.Area = function (o, o2) {
    Tee.CustomSeries.call(this, o, o2);

    this.useOrigin = false;
    this.origin = 0;
    this.drawLine = true;
    this.closeArea = true;

    var f = this.format;
    f.shadow.visible = true;
    f.lineCap = "round";
    f.stroke.fill = "black";
    f.fill = "";
    f.beveled = true;

    f.depth = 1;
    f.z = 0.5;

    var r = new Rectangle();

    this.draw = function () {
      var len = this.data.values.length;

      if (len > 0) {
        var a = this.mandatoryAxis,
          nm = this.notmandatory,
          originPos,
          isY = this.yMandatory;

        if (this.useOrigin) originPos = a.calc(this.origin);
        else if ((isY && a.inverted) || (!isY && !a.inverted))
          originPos = a.startPos;
        else originPos = a.endPos;

        this.isStacked = this.stacked != "no";
        this.isStack100 = this.stacked == "100";

        var start,
          p = new Point(),
          old,
          t,
          c = this.chart.ctx,
          smop,
          doStack = this.isStacked, // && (visibleBar.index>0),
          begin = 0,
          end = len;

        if (!this.smooth && !this.data.x) {
          begin = Math.max(0, trunc(nm.minimum) - 1);
          end = Math.min(len, trunc(nm.maximum) + 2);
        }

        c.depth = f.depth;
        c.z = f.z;

        var closePoint;

        c.beginPath();

        if (this.smooth > 0 && typeof Tee.drawSpline !== "undefined") {
          smop = new Array(2 * len);

          for (t = 0; t < len; t++) {
            this.calc(t, p);

            smop[2 * t] = p.x;
            smop[2 * t + 1] = p.y;
          }

          start = isY ? smop[0] : smop[1];

          if (c.spline) c.spline(smop, true);
          else Tee.drawSpline(c, smop, this.smooth, true);

          if (doStack) {
            var tmp = 0;

            for (t = len - 1; t >= 0; t--) {
              this.calcStack(t, p, 0);
              smop[tmp++] = p.x;
              smop[tmp++] = p.y;
            }

            c.lineTo(smop[0], smop[1]);

            if (c.spline) c.spline(smop, true);
            else Tee.drawSpline(c, smop, this.smooth, false);
          }
        }
        //  if (!this.isNull(t)) <-- pending
        else {
          this.calc(begin, p);
          c.moveTo(p.x, p.y);
          start = isY ? p.x : p.y;
          old = isY ? p.y : p.x;

          if (this.stairs)
            for (t = begin + 1; t < end; t++) {
              this.calc(t, p);
              c.lineTo(p.x, old);
              c.lineTo(p.x, p.y);
              old = isY ? p.y : p.x;
            }
          else
            for (t = begin + 1; t < end; t++) {
              this.calc(t, p);
              c.lineTo(p.x, p.y);
            }
        }

        if (doStack) {
          if (this.smooth === 0)
            for (t = end - 1; t >= begin; t--) {
              this.calcStack(t, p, 0);
              if (this.stairs) {
                c.lineTo(p.x, old);
                c.lineTo(p.x, p.y);
                old = isY ? p.y : p.x;
              } else c.lineTo(p.x, p.y);
            }
        } else {
          if (isY) {
            if (this.closeArea) {
              c.lineTo(p.x, originPos);
              c.lineTo(start, originPos);
            } else closePoint = p;
          } else {
            c.lineTo(originPos, p.y);
            c.lineTo(originPos, start);
          }
        }

        if (!this.closeArea) {
          var st = f.stroke;
          var s = st.fill;
          st.prepare(s);
          c.stroke();
          c.lineTo(closePoint.x, originPos);
          c.lineTo(start, originPos);
          var tmpStrokeFill = f.stroke.fill;
          f.stroke.fill = "#00FF0000";
        }

        c.closePath();

        var g = f.gradient;
        if (g.visible) g.colors[g.colors.length - 1] = f.fill;

        this.bounds(r);

        if (c.__webgl) c.beveled = f.beveled;

        f.draw(c, null, r);

        if (!this.closeArea) f.stroke.fill = tmpStrokeFill;

        if (this.pointer.visible) this.drawPointers();
      }
    };

    this.minYValue = function () {
      var v = this.yMandatory
        ? Tee.Series.prototype.minYValue.call(this)
        : Tee.Series.prototype.minXValue.call(this);
      return this.yMandatory
        ? this.useOrigin
          ? Math.min(v, this.origin)
          : v
        : v;
    };

    this.minXValue = function () {
      var v = this.yMandatory
        ? Tee.Series.prototype.minXValue.call(this)
        : Tee.Series.prototype.minYValue.call(this);
      return this.yMandatory
        ? v
        : this.useOrigin
        ? Math.min(v, this.origin)
        : v;
    };

    this.maxYValue = function () {
      var v = this.yMandatory
        ? this.stackMaxValue()
        : Tee.Series.prototype.maxXValue.call(this);
      return this.yMandatory
        ? this.useOrigin
          ? Math.max(v, this.origin)
          : v
        : v;
    };

    this.maxXValue = function () {
      var v = this.yMandatory
        ? Tee.Series.prototype.maxXValue.call(this)
        : this.stackMaxValue();
      return this.yMandatory
        ? v
        : this.useOrigin
        ? Math.max(v, this.origin)
        : v;
    };

    this.vertMargins = function (p) {
      if (this.yMandatory && f.stroke.fill !== "") p.y += f.stroke.size + 2;
    };

    this.horizMargins = function (p) {
      if (!this.yMandatory && f.stroke.fill !== "") p.y += f.stroke.size + 2;
    };
  };

  Tee.Area.prototype = new Tee.CustomSeries();
  
  
  Tee.HighLowBar = function (o, o2) {
    Tee.CustomSeries.call(this, o, o2);

    this.useOrigin = false;
    this.origin = 0;
    this.drawLine = true;
    this.closeArea = true;

    var f = this.format;
    f.shadow.visible = true;
    f.lineCap = "round";
    f.stroke.fill = "black";
    f.fill = "";
    f.beveled = true;
	
	this.maxmin = new Tee.Format(f.chart);
	var b = (this.maxmin);
    b.shadow.visible = false;
    b.lineCap = "round";
    b.stroke.fill = "red";
    b.fill = "";
    b.beveled = true;

    f.depth = 1;
    f.z = 0.5;
	
    this.data.lows = [];

    this.addRandom = function (count) {
      var d = this.data;

      if (!count) count = 5;

      d.values.length = count;

      d.x = null;
      d.lows = [];
      d.lows.length = count;

      if (count > 0) {
        for (var t = 0; t < count; t++) {
          d.values[t] = Math.random() * 1000;
          d.lows[t] = 50 + Math.random() * 150;
        }
      }
    };	
	
	this.calcLows = function (index, p) {
      this.isStacked
        ? this.calcStack(index, p, this.data.lows[index])
        : Tee.Series.prototype.calc.call(this, index, p);
    };

    var r = new Rectangle();

    this.draw = function () {
      var len = this.data.values.length;

      if (len > 0) {
        var a = this.mandatoryAxis,
          nm = this.notmandatory,
          originPos,
          isY = this.yMandatory;

        if (this.useOrigin) originPos = a.calc(this.origin);
        else if ((isY && a.inverted) || (!isY && !a.inverted))
          originPos = a.startPos;
        else originPos = a.endPos;

        this.isStacked = this.stacked != "no";
        this.isStack100 = this.stacked == "100";

        var start,
          p = new Point(),
          old,
          t,
          c = this.chart.ctx,
          smop,
          doStack = this.isStacked, // && (visibleBar.index>0),
          begin = 0,
          end = len;

        if (!this.smooth && !this.data.x) {
          begin = Math.max(0, trunc(nm.minimum) - 1);
          end = Math.min(len, trunc(nm.maximum) + 2);
        }

        c.depth = f.depth;
        c.z = f.z;

        var closePoint;
		var maxP = [];
		var minP = [];
		var idx=0;

        c.beginPath();

        if (this.smooth > 0 && typeof Tee.drawSpline !== "undefined") {
          smop = new Array(2 * len);

          for (t = 0; t < len; t++) {
            this.calc(t, p);

            smop[2 * t] = p.x;
            smop[2 * t + 1] = p.y;
          }

          start = isY ? smop[0] : smop[1];

          if (c.spline) c.spline(smop, true);
          else Tee.drawSpline(c, smop, this.smooth, true);

          if (doStack) {
            var tmp = 0;

            for (t = len - 1; t >= 0; t--) {
              this.calcStack(t, p, 0);
              smop[tmp++] = p.x;
              smop[tmp++] = p.y;
            }

            c.lineTo(smop[0], smop[1]);

            if (c.spline) c.spline(smop, true);
            else Tee.drawSpline(c, smop, this.smooth, false);
          }
        }
        //  if (!this.isNull(t)) <-- pending
        else {
          this.calc(begin, p);
          c.moveTo(p.x, p.y);
          start = isY ? p.x : p.y;
          old = isY ? p.y : p.x;

          if (this.stairs)
            for (t = begin + 1; t < end; t++) {
              this.calc(t, p);
              c.lineTo(p.x, old);
              c.lineTo(p.x, p.y);
              old = isY ? p.y : p.x;
            }
          else if (this.rects)
            for (t = begin; t < end; t++) {
			  //var startPx = p.x;
			  var lowP = p;
              this.calc(t, p);
			  var lowPY = this.mandatoryAxis.calc(this.data.lows[t]);
			  var nextPx = this.notmandatory.calc(this.data.x[t+1]);
			  //this.calcLows(t, lowP);
			  c.rect(p.x, p.y, nextPx-p.x, lowPY-p.y);
              //c.lineTo(p.x, old);
              //c.lineTo(p.x, p.y);
              old = isY ? p.y : p.x;
			  
			  maxP[idx]=new Point(p.x, p.y);
			  minP[idx]=new Point(p.x, lowPY);
			  idx++;
			  maxP[idx]=new Point(nextPx, p.y);
			  minP[idx]=new Point(nextPx, lowPY);
			  idx++;
			   
            }
          else
            for (t = begin + 1; t < end; t++) {
              this.calc(t, p);
              c.lineTo(p.x, p.y);
            }
        }

        if (doStack) {
          if (this.smooth === 0)
            for (t = end - 1; t >= begin; t--) {
              this.calcStack(t, p, 0);
              if (this.stairs) {
                c.lineTo(p.x, old);
                c.lineTo(p.x, p.y);
                old = isY ? p.y : p.x;
              } else c.lineTo(p.x, p.y);
            }
        } else {
          if (isY) {
            if (this.closeArea) {
              c.lineTo(p.x, originPos);
              c.lineTo(start, originPos);
            } else closePoint = p;
          } else {
            c.lineTo(originPos, p.y);
            c.lineTo(originPos, start);
          }
        }
		
		//c.polygon(maxP);

        /*if (!this.closeArea) {
          var st = f.stroke;
          var s = st.fill;
          st.prepare(s);
          c.stroke();
          c.lineTo(closePoint.x, originPos);
          c.lineTo(start, originPos);
        }*/

        c.closePath();

        var g = f.gradient;
        if (g.visible) g.colors[g.colors.length - 1] = f.fill;

        this.bounds(r);

        if (c.__webgl) c.beveled = f.beveled;

        f.draw(c, null, r);
		
		c.beginPath();
		for (t = 0; t < maxP.length; t++) {
			c.lineTo(maxP[t].x, maxP[t].y);
		}
		//if (maxP.length>0)
		//  c.moveTo(maxP[maxP.length-1].x+1, maxP[maxP.length-1].y+1);
		//c.closePath();
		f.stroke.prepare(f.fill, c);
		c.stroke();

        var oldStroke = f.stroke.fill;
		var oldFill = f.fill;
		var oldSize = f.stroke.size;
		f.stroke.fill="white";
		f.stroke.size = 0.75;
		f.fill = "rgba(123,0,0,0.0)"
	    f.draw(c, null, r);
		f.stroke.fill=oldStroke;
		f.fill = oldFill;
		f.stroke.size = oldSize;
		
		c.beginPath();
		for (t = 0; t < minP.length; t++) {
			c.lineTo(minP[t].x, minP[t].y);
		}
		//if (minP.length>0)
		  //c.moveTo(minP[minP.length-1].x+1, minP[minP.length-1].y+1);
	  
	    //c.closePath();

        var oldStroke = f.stroke.fill;
		var oldFill = f.fill;
		var oldSize = f.stroke.size;
		f.stroke.fill="white";
		f.stroke.size = 0.75;
		f.fill = "rgba(123,0,0,0.0)"
		f.draw(c, null, r);
		f.stroke.fill=oldStroke;
		f.fill = oldFill;		
		f.stroke.size = oldSize;
		
		f.stroke.prepare(f.fill, c);
		c.stroke();
		
		//this.format.stroke.prepare(this.format.stroke.fill, c);
        //c.stroke();

        if (this.pointer.visible) this.drawPointers();
      }
    };

    this.minYValue = function () {
      var v = this.yMandatory
        ? Tee.Series.prototype.minYValue.call(this)
        : Tee.Series.prototype.minXValue.call(this);
      return this.yMandatory
        ? this.useOrigin
          ? Math.min(v, this.origin)
          : v
        : v;
    };

    this.minXValue = function () {
      var v = this.yMandatory
        ? Tee.Series.prototype.minXValue.call(this)
        : Tee.Series.prototype.minYValue.call(this);
      return this.yMandatory
        ? v
        : this.useOrigin
        ? Math.min(v, this.origin)
        : v;
    };

    this.maxYValue = function () {
      var v = this.yMandatory
        ? this.stackMaxValue()
        : Tee.Series.prototype.maxXValue.call(this);
      return this.yMandatory
        ? this.useOrigin
          ? Math.max(v, this.origin)
          : v
        : v;
    };

    this.maxXValue = function () {
      var v = this.yMandatory
        ? Tee.Series.prototype.maxXValue.call(this)
        : this.stackMaxValue();
      return this.yMandatory
        ? v
        : this.useOrigin
        ? Math.max(v, this.origin)
        : v;
    };

    this.vertMargins = function (p) {
      if (this.yMandatory && f.stroke.fill !== "") p.y += f.stroke.size + 2;
    };

    this.horizMargins = function (p) {
      if (!this.yMandatory && f.stroke.fill !== "") p.y += f.stroke.size + 2;
    };
  };  
  
  Tee.HighLowBar.prototype = new Tee.CustomSeries();

  /**
   * @constructor
   * @augments Tee.Area
   * @class Horizontal area style
   */
  Tee.HorizArea = function (o, o2) {
    Tee.Area.call(this, o, o2);
    this.yMandatory = false;
  };
  Tee.HorizArea.prototype = new Tee.Area();

  /**
   * @constructor
   * @augments Tee.Pie
   * @class Draws series data as slices of a circle, with a center hole
   * @property {Number} [donut=50] Percent of hole size relative to pie radius. From 0 to 100.
   */
  Tee.Donut = function (o, o2) {
    var lessDonutWidth = 100;
    var donutArray = [];
    Tee.Pie.call(this, o, o2);
    this.donut = 50;

    this.refreshWidth = function () {
      if (this.concentric) {
        donutArray = this.chart.series.items;
        var nVisibleDonuts = 0;
        var n = 0;
        for (var i = 0; i < donutArray.length; i++) {
          if (donutArray[i].visible) nVisibleDonuts++;
        }
        if (lessDonutWidth == 100) getLessDonutWidth();
        for (var i = 0; i < donutArray.length; i++) {
          if (donutArray[i].visible) {
            donutArray[i].donut =
              lessDonutWidth + n * ((100 - lessDonutWidth) / nVisibleDonuts);
            n++;
          }
        }
      }
    };
    function getLessDonutWidth() {
      for (var i = 0; i < donutArray.length; i++) {
        if (donutArray[i].donut < lessDonutWidth)
          lessDonutWidth = donutArray[i].donut;
      }
    }
  };

  Tee.Donut.prototype = new Tee.Pie();

  /**
   * @constructor
   * @augments Tee.Donut
   * @class Draws comparative of values plotted as concentric circular bands
   * @property {Number} [rotation=270] Rotates all slices by specified degree from 0 to 360.
   * @property {Number} [angleWidth=values%] Indicates the width of the Donut/Pie/ActivityGauge in degrees form 0 to 360.
   */

  Tee.ActivityGauge = function (o, o2) {
    Tee.Donut.call(this, [], []);
    this.data = {
      values: o != null ? o : [],
      labels: o2 != null ? o2 : [],
    };

    this.donutArray = [];
    this.maxWidth = 230;
    this.maxDrawWidth = this.maxWidth;
    this.addRandom = function (count) {
      for (var i = 0; i < count; i++) {
        this.add(
          Math.floor(Math.random() * 20 + 10),
          String.fromCharCode(65 + i)
        );
      }
      return this;
    };
    this.maxValue = function () {
      var tmp = this.data.values[0];
      for (var i = 0; i < this.data.values.length; i++) {
        if (tmp < this.data.values[i]) tmp = this.data.values[i];
      }
      return tmp;
    };

    if (o != null) {
      for (var i = 0; i < o.length; i++) {
        var donutCenterSize = 40;
        var tmpDonut =
          donutCenterSize + ((100 - donutCenterSize) * i) / o.length;
        var tmpMaxRadius =
          donutCenterSize + ((100 - donutCenterSize) * (i + 1)) / o.length;
        var tmpAngleWidth = Math.abs(
          (this.maxDrawWidth * o[o.length - 1 - i]) / this.maxValue()
        );
        this.donutArray.push(
          createDonut(
            o[o.length - 1 - i],
            o2[o.length - 1 - i],
            tmpDonut,
            tmpMaxRadius,
            tmpAngleWidth
          )
        );
      }
    }

    this.clicked = function (p) {
      var index = -1,
        i = 0;
      while (i < this.donutArray.length && index == -1) {
        if (index == -1 && this.donutArray[i].clicked(p) != -1)
          index = this.donutArray.length - i - 1;
        i++;
      }
      return index;
    };
    function createDonut(value, label, tmpDonut, maxRadius, angleWidth) {
      var donut = new Tee.Donut([value], [label]);
      donut.concentric = true;
      donut.marks.visible = false;
      donut.format.shadow.visible = false;
      donut.format.gradient.visible = false;
      donut.donut = tmpDonut;
      donut.maxRadius = maxRadius;
      donut.angleWidth = angleWidth;
      donut.rotation = 270;
      donut.visible = false;
      return donut;
    }

    function copyFormat(donut, origin, colorNum) {
      var dF = donut.format;
      var f = origin.format;
      dF.fill =
        origin.chart.palette.colors[
          origin.donutArray.length -
            1 -
            (colorNum % origin.chart.palette.colors.length)
        ];
      dF.font.baseLine = f.font.baseLine;
      dF.font.fill = f.font.fill;
      dF.font.style = f.font.style;
      dF.font.textAlign = f.font.textAlign;
      dF.gradient.colors = [f.gradient.colors[0][colorNum]];
      dF.gradient.direction = f.gradient.direction;
      dF.gradient.offset.x = f.gradient.offset.x;
      dF.gradient.offset.y = f.gradient.offset.y;
      dF.gradient.stops = f.gradient.stops;
      dF.gradient.visible = f.gradient.visible;
      dF.round.x = f.round.x;
      dF.round.y = f.round.y;
      dF.shadow.blur = f.shadow.blur;
      dF.shadow.color = f.shadow.color;
      dF.shadow.height = f.shadow.height;
      dF.shadow.visible = f.shadow.visible;
      dF.shadow.width = f.shadow.width;
      dF.stroke.fill = f.stroke.fill.slice(0);
      dF.stroke.cap = f.stroke.cap;
      dF.stroke.dash = f.stroke.dash;
      dF.stroke.join = f.stroke.join;
      dF.stroke.size = f.stroke.size;
      dF.transparency = f.transparency;
      donut.fill = function (i) {
        return dF.gradient.visible ? dF.gradient.colors : dF.fill;
      };
    }
    this.recalcWidth = function () {
      for (var i = 0; i < this.donutArray.length; i++) {
        var donutCenterSize = 40;
        this.donutArray[i].donut =
          donutCenterSize +
          ((100 - donutCenterSize) * i) / this.data.values.length;
        this.donutArray[i].maxRadius =
          donutCenterSize +
          ((100 - donutCenterSize) * (i + 1)) / this.data.values.length;
        this.donutArray[i].angleWidth = Math.abs(
          (this.maxDrawWidth *
            this.data.values[this.data.values.length - 1 - i]) /
            this.maxValue()
        );
      }
    };

    this.minValue = function () {
      var tmp = this.data.values[0];
      for (var i = 0; i < this.data.values.length; i++) {
        if (tmp > this.data.values[i]) tmp = this.data.values[i];
      }
      return tmp;
    };
    this.add = function (value, label) {
      var donutTmp = createDonut(value, label, 0, 0, 0);
      this.donutArray.push(donutTmp);
      this.data.values.push(value);
      this.data.labels.push(label);
      if (this.chart != null) this.linkDonutsToChart();
    };
    this.draw = function () {
      for (var i = 0; i < this.donutArray.length; i++) {
        var c = this.donutArray[i].chart.ctx;
        copyFormat(this.donutArray[i], this, i);
        c.fillStyle = this.donutArray[i].getFillStyle(
          this.donutArray[i].chart.chartRect,
          this.donutArray[i].format.fill
        );
        this.recalcWidth();
        this.donutArray[i].draw();
      }
    };
    this.linkDonutsToChart = function () {
      for (var i = 0; i < this.donutArray.length; i++) {
        this.donutArray[i].setChart(this.donutArray[i], this.chart);
      }
    };
  };
  Tee.ActivityGauge.prototype = new Tee.Donut();

  /**
   * @constructor
   * @augments Tee.Series
   * @class Draws series data as Gantt horizontal bars with start and end datetime values.
   * @property {Number} [height=70] Percent of gantt bar height. From 0 to 100.
   */
  Tee.Gantt = function (o, o2) {
    Tee.Series.call(this, o, o2);

    this.yMandatory = false;

    this.dateFormat = "mediumDate";

    this.hover.enabled = true;
    this.hover.round.x = this.hover.round.y = 8;

    this.nextTasks = [];
    this.nextTasksStrokeStyle = "Black";
    this.nextTasksPosition = "back"; //back, front
    this.colorEach = "yes";

    this.data.start = this.data.values;
    this.data.x = [];
    this.data.end = [];

    this.height = 70;
    this.margin = new Point(6, 6);
    this.continuous = false;
    var f = this.format;
    f.shadow.visible = true;
    f.round.x = f.round.y = 8;
    f.stroke.fill = "black";
    f.gradient.visible = true;

    var r = new Rectangle(),
      _h;

    this.addNextTask = function (point1, point2) {
      this.nextTasks.push([point1, point2]);
    };

    this.addRandom = function (count) {
      if (!count) count = 5;

      var d = this.data;

      d.x.length = count;
      d.start.length = count;
      d.end.length = count;

      if (count > 0) {
        var year = 2012,
          month,
          day;

        for (var t = 0; t < count; t++) {
          d.x[t] = t;

          month = trunc(Math.random() * 12);
          day = trunc(Math.random() * 10);
          d.start[t] = new Date(year, month, day);
          if (month < 5) month = 5 + trunc(Math.random() * 7);
          d.end[t] = new Date(year, month, day + Math.random() * 10);
        }
      }
    };

    this.bounds = function (index, r) {
      if (this.isNull(index)) return false;
      else {
        this.calc(index, r);
        r.y -= _h * 0.5;
        r.width = this.data.end
          ? this.mandatoryAxis.calcSize(
              this.data.end[index] - this.data.start[index]
            )
          : 0;
        r.height = _h;

        return true;
      }
    };

    this.add = function (pos, label, start, end) {
      var d = this.data;
      d.labels.push(label);
      d.x.push(pos);
      d.start.push(start);
      d.end.push(end);
    };

    this.clicked = function (p) {
      var len = this.data.values.length,
        t;

      for (t = 0; t < len; t++)
        if (this.bounds(t, r) && r.contains(p)) return t;

      return -1;
    };

    this.draw = function () {
      var len = this.data.values.length,
        t,
        ff,
        hover = this.hover,
        oldFill = hover.fill,
        c = this.chart.ctx,
        punts = [];
      if (this.nextTasksPosition == "back") {
        drawLines(this);
      }
      _h = this.notmandatory.calcSize(this.height * 0.01);

      for (t = 0; t < len; t++)
        if (this.bounds(t, r)) {
          ff = hover.enabled && this.over === t ? hover : f;
          ff.fill = this.getFillStyle(r, this.getFill(t, ff));
          ff.rectangle(r);
        }

      hover.fill = oldFill;
      if (this.nextTasksPosition == "front") {
        drawLines(this);
      }
      function drawLines(gantt) {
        for (var i = 0; i < gantt.nextTasks.length; i++) {
          c.beginPath();
          c.strokeStyle = gantt.nextTasksStrokeStyle;
          c.lineWidth = 2;
          c.fillStyle = "000000";
          punts.push(
            Math.round(
              gantt.chart.axes.bottom.calc(
                gantt.data.end[gantt.nextTasks[i][0]]
              )
            )
          );
          punts.push(
            Math.round(
              gantt.chart.axes.left.calc(gantt.data.x[gantt.nextTasks[i][0]])
            )
          );
          punts.push(
            Math.round(
              gantt.chart.axes.bottom.calc(
                gantt.data.start[gantt.nextTasks[i][1]]
              )
            )
          );
          punts.push(
            Math.round(
              gantt.chart.axes.left.calc(gantt.data.x[gantt.nextTasks[i][1]])
            )
          );

          c.moveTo(punts[0], punts[1]);
          c.lineTo(punts[0] - (punts[0] - punts[2]) / 2, punts[1]);
          c.lineTo(punts[0] - (punts[0] - punts[2]) / 2, punts[3]);
          c.lineTo(punts[2], punts[3]);

          c.stroke();

          punts = [];
        }
      }
    };

    this.horizMargins = function (p) {
      p.x = this.margin.x;
      p.y = this.margin.y;
    };

    this.minYValue = function () {
      return this.parent.minXValue.call(this) - 0.5;
    };

    this.maxYValue = function () {
      return this.parent.maxXValue.call(this) + 0.5;
    };

    this.minXValue = function () {
      return ArrayMin(this.data.start);
    };

    this.maxXValue = function () {
      return ArrayMax(this.data.end);
    };
  };

  Tee.Gantt.prototype = new Tee.Series();
  Tee.Gantt.prototype.parent = Tee.Series.prototype;

  /**
   * @constructor
   * @augments Tee.PointXY
   * @class Draws data as points, each one with a different size or radius
   * @property {Object} data Contains each bubble x, value and radius.
   * @property {Number[]} data.radius Defines each bubble radius value.
   */
  Tee.Bubble = function (o, o2) {
    Tee.PointXY.call(this, o, o2);

    var p = this.pointer;
    p.colorEach = true;
    p.style = "sphere";
    p.format.gradient.visible = true;
    p.format.gradient.direction = "radial";

    /**
     * When true, horizontal and vertical edge margins are calculated.
     */
    this.inflate = true;

    this.data.radius = [];

    this.addRandom = function (count) {
      var d = this.data;

      if (!count) count = 5;

      d.values.length = count;

      d.x = null;
      d.radius = [];
      d.radius.length = count;

      if (count > 0) {
        for (var t = 0; t < count; t++) {
          d.values[t] = Math.random() * 1000;
          d.radius[t] = 50 + Math.random() * 150;
        }
      }
    };
  };

  Tee.Bubble.prototype.initZ = function () {
    this.parent.prototype.initZ.call(this);
    this.format.marks.z = this.format.z - 1;
  };

  Tee.Bubble.prototype = new Tee.PointXY();

  Tee.Bubble.prototype.getSize = function (index) {
    var s = this.data.radius
      ? this._vertAxis.calcSize(this.data.radius[index])
      : 0;
    this.pointer.width = s;
    this.pointer.height = s;
  };

  Tee.Bubble.prototype.horizMargins = function (p) {
    this.calcWidth = function (index) {
      this.getSize(index);
      var res = 1 + this.pointer.width * 0.5,
        s = this.pointer.format.stroke;
      if (s.fill !== "") res += s.size;
      return res;
    };

    if (this.pointer.visible && this.inflate) {
      p.x = this.calcWidth(0);
      p.y = this.calcWidth(this.count() - 1);
    }
  };

  Tee.Bubble.prototype.vertMargins = function (p) {
    this.calcHeight = function (index) {
      this.getSize(index);
      var res = 1 + this.pointer.height * 0.5,
        s = this.pointer.format.stroke;
      if (s.fill !== "") res += s.size;
      return res;
    };

    if (this.pointer.visible && this.inflate) {
      var low,
        high,
        lowIndex = 0,
        highIndex = 0,
        l = this.count(),
        pos = { x: 0, y: 0 };

      if (l > 0) {
        this.calc(0, pos);
        low = high = pos.y;

        for (var t = 1; t < l; t++) {
          this.calc(t, pos);

          if (pos.y < low) lowIndex = t;
          else if (pos.y > high) highIndex = t;
        }

        p.x = this.calcHeight(highIndex);
        p.y = this.calcHeight(lowIndex);
      }
    }
  };

  /**
   * @constructor
   * @augments Tee.Bar
   * @class Draws financial Volume data as thin Bar lines.
   */
  Tee.Volume = function (o, o2) {
    Tee.Bar.call(this, o, o2);

    this.barStyle = "line";
    this.marks.visible = false;
    this.colorEach = false;
    var f = this.format;
    f.shadow.visible = false;
    f.gradient.visible = false;
    f.stroke.fill = "";
  };
  Tee.Volume.prototype = new Tee.Bar();

  /**
   * @constructor
   * @augments Tee.PointXY
   * @class Draws financial OHLC data as Candle or CandleBar points.
   * @property {String} style Defines candle style ("candle", "bar", "openclose").
   */
  Tee.Candle = function (o, o2) {
    Tee.PointXY.call(this, o, o2);

    var f = this.format;
    f.z = 0.5;
    f.depth = 0.1;

    this.pointer.width = 7;
    this.pointer.format.stroke.visible = false;

    var hi = (this.higher = this.pointer.format);
    hi.fill = "green";

    var lo = (this.lower = new Tee.Format(this.chart));
    lo.fill = "red";
    lo.stroke.visible = false;

    this.style = "candle";

    /*
     * @private
     */
    this.setChart = function (series, chart) {
      var tmp = Tee.PointXY.prototype.setChart;
      tmp(series, chart);
      lo.setChart(chart);
    };

    this.draw = function () {
      var d = this.data,
        len = d.values.length,
        t,
        p = new Point(),
        po = this.pointer,
        w = po.width * 0.5,
        o,
        h,
        l,
        m = this.mandatoryAxis,
        y,
        he,
        c = this.chart.ctx,
        col,
        x,
        r;

      c.z = f.z + f.depth * 0.5;

      for (t = 0; t < len; t++)
        if (!this.isNull(t)) {
          this.calc(t, p);
          x = p.x;

          o = m.calc(d.open[t]);
          h = m.calc(d.high[t]);
          l = m.calc(d.low[t]);

          if (p.y > o) {
            y = o;
            he = p.y - o;
            col = lo;
          } else {
            y = p.y;
            he = o - y;
            col = hi;
          }

          if (this.style == "bar") {
            c.beginPath();

            c.moveTo(x, h);
            c.lineTo(x, l);
            c.moveTo(x - w, o);
            c.lineTo(x, o);
            c.moveTo(x, p.y);
            c.lineTo(x + w, p.y);

            col.stroke.prepare(col.fill);

            c.stroke();
          } else {
            col.depth = w / 100; // totalDepth*0.5 !!
            col.z = f.z + f.depth * 0.5 - col.depth * 0.5;

            r = { x: x - w, y: y, width: po.width, height: he };

            if (this.pointer.style === "cylinder") col.cylinder(r, 1, true);
            else col.cube(r);

            col.draw(c, null, r);

            if (this.hover.enabled && this.over == t)
              this.hover.rectangle(x - w, y, po.width, he);
          }

          if (this.style != "openclose")
            if (h < y || l > y + he) {
              c.z = f.z + f.depth * 0.5;

              c.beginPath();

              c.moveTo(x, y);
              c.lineTo(x, h);

              c.moveTo(x, y + he);
              c.lineTo(x, l);

              if (this.hover.enabled && this.over == t)
                this.hover.stroke.prepare(col.fill);
              else col.stroke.prepare(col.fill);

              c.stroke();
            }
        }
    };

    this.minYValue = function () {
      return this.data.low.length > 0 ? ArrayMin(this.data.low) : 0;
    };

    this.maxYValue = function () {
      return this.data.high.length > 0 ? ArrayMax(this.data.high) : 0;
    };

    this.addRandom = function (count) {
      var d = this.data;
      if (!count) count = 10;
      d.values.length = count;
      d.close = d.values;
      if (d.open) d.open.length = count;
      else d.open = new Array(count);
      if (d.high) d.high.length = count;
      else d.high = new Array(count);
      if (d.low) d.low.length = count;
      else d.low = new Array(count);

      if (count > 0) {
        var tmp = 25 + Math.random() * 100,
          o;

        for (var t = 0; t < count; t++) {
          o = d.open[t] = tmp;
          tmp = d.close[t] = tmp + Math.random() * 25 - 12.5;
          d.high[t] = Math.max(o, tmp) + Math.random() * 15;
          d.low[t] = Math.min(o, tmp) - Math.random() * 15;
        }
      }
    };
  };

  Tee.Candle.prototype = new Tee.PointXY();

  Tee.Candle.prototype.clicked = function (p) {
    var w = this.pointer.width,
      m = this.mandatoryAxis,
      n = this.notmandatory,
      d = this.data,
      len = d.values.length,
      r = new Rectangle(),
      t,
      o,
      c;

    r.width = w;

    for (t = 0; t < len; t++)
      if (!this.isNull(t)) {
        r.x = n.calc(t) - w * 0.5;

        (o = m.calc(d.open[t])), (c = m.calc(d.close[t]));
        r.y = o > c ? c : o;
        r.height = Math.abs(o - c);

        if (r.contains(p)) return t;
      }

    return -1;
  };

  Tee.Candle.prototype.vertMargins = function () {};

  /**
   * @constructor
   * @augments Tee.CustomSeries
   * @class Draws values as Polar / Radar charts.
   * @property {Number} [rotation = 0] Rotates polar points, from 0 to 360 degree.
   * @property {Boolean} [clockwise = true] Direction of Values.
   */
  Tee.Polar = function (o, o2) {
    Tee.CustomSeries.call(this, o, o2);

    this.pointer.visible = true;
    this.rotation = 0; // degress from 0 to 360

    this._paintAxes = false;
    this._paintWalls = false;
    this.continuous = false;
    this.useOrigin = false;
    this.origin = 0;
	
	this.clockwise = true;

    var p = { x: 0, y: 0 },
      pp,
      f = this.format,
      center = { x: 0, y: 0 },
      radius,
      pi180 = Math.PI / 180;

    f.stroke.fill = "black";
    f.z = 0.5;

    this.calc = function (index, p) {
      var d = this.data,
        v = d.values[index],
        mand = this.mandatoryAxis;

      var x = d.x ? d.x[index] : (360 * index) / d.values.length,
        dif = mand.inverted ? mand.maximum - v : v - mand.minimum,
        angle = this.clockwise ? (pi180 * (this.rotation + x)) : -(pi180 * (this.rotation + x)),
        rad = (dif * radius) / (mand.maximum - mand.minimum);

      p.x = center.x + Math.cos(angle) * rad;
      p.y = center.y + Math.sin(angle) * rad;
    };

    function tryDrawAxis(axis, px, py) {
      if (axis.visible) {
        var old = axis.axisPos;
        axis.axisPos = px;
        axis.startPos = py - radius;
        axis.endPos = py + radius;

        var oldz = axis.z;
        axis.z = 1 - axis.chart.walls.back.size * 0.5 - 0.1;

        axis.drawAxis();

        axis.axisPos = old;
        axis.z = oldz;
      }
    }

    function calcCenter(r, axis) {
      var rw = r.width,
        rh = r.height;
      center.x = r.x + 0.5 * rw;
      center.y = r.y + 0.5 * rh;
      radius = Math.min(rw, rh) * 0.5;

      if (axis.visible && axis.labels.visible) {
        var textH = axis.labels.format.textHeight("W");
        radius -= textH;
      }
    }

    this.beforeDraw = function () {
      var oldz;

      calcCenter(this.chart.chartRect, this.notmandatory);

      // Background:

      var walls = this.chart.walls;

      if (walls.visible) {
        var wall = walls.back;

        if (wall.visible) {
          var wallFormat = wall.format;

          oldz = wallFormat.z;
          wallFormat.z = 1;
          wallFormat.ellipse(center.x, center.y, 2 * radius, 2 * radius);
          wallFormat.z = oldz;
        }
      }

      if (this.chart.axes.visible) {
        var nomand = this.notmandatory,
          nomandgrid = nomand.grid.format.stroke,
          mand = this.mandatoryAxis,
          vmin = 0,
          ctx = this.chart.ctx,
          angle,
          labelincrem = 10,
          nomandrotation = nomand.rotation || 0;

        // Axis Labels

        if (nomand.visible && nomand.labels.visible) {
          var rText = { x: 0, y: 0, width: 0, height: 0 },
            labelsF = nomand.labels.format,
            textH = labelsF.textHeight("W"),
            labelRadius = radius + textH * 0.8;

          labelincrem = Math.max(10, 90 / trunc(radius / textH));

          oldz = labelsF.z;
          labelsF.z = 0.6;

          vmin = 0;
          while (vmin < 360) {
            angle = this.clockwise ? (pi180 * (vmin + nomandrotation)) : - (pi180 * (vmin + nomandrotation));

            rText.x = center.x + Math.cos(angle) * labelRadius;
            rText.y = center.y + Math.sin(angle) * labelRadius - textH * 0.5;

            labelsF.drawText(rText, "" + vmin);

            vmin += labelincrem;
          }

          labelsF.z = oldz;
        }

        // line grids

        if (nomand.visible && nomand.grid.visible) {
          var gridincrem = nomand.increment || 10;

          ctx.z = 1 - walls.back.size * 0.5 - 0.1;
          ctx.beginPath();

          vmin = 0;

          while (vmin < 360) {
            angle = pi180 * (vmin + nomandrotation);

            ctx.moveTo(center.x, center.y);
            ctx.lineTo(
              center.x + Math.cos(angle) * radius,
              center.y + Math.sin(angle) * radius
            );

            vmin += gridincrem;
          }

          nomandgrid.prepare(nomandgrid.fill, ctx);
          ctx.stroke();
        }

        // circle grids

        if (mand.visible && mand.grid.visible) {
          vmin = mand.roundMin();

          var rad,
            mandgrid = mand.grid.format,
            gridstep = (2 * radius) / (mand.maximum - mand.minimum);

          oldz = mandgrid.z;

          mandgrid.z = 1 - walls.back.size * 0.5 - 0.1;
          ctx.z = mandgrid.z;

          while (vmin < mand.maximum) {
            rad = (vmin - mand.minimum) * gridstep;
            mandgrid.ellipse(center.x, center.y, rad, rad);
            vmin += mand.increm;
          }

          mandgrid.z = oldz;
        }

        tryDrawAxis(mand, center.x, center.y);
        tryDrawAxis(nomand, center.y, center.x);
      }
    };

    this.draw = function () {
      calcCenter(this.chart.chartRect, this.notmandatory);

      var len = this.data.values.length,
        t;

      // Draw Points

      pp = [];

      for (t = 0; t < len; t++)
        if (!this.isNull(t)) {
          this.calc(t, p);
          pp.push({ x: p.x, y: p.y });
        }

      len = pp.length;

      if (len > 0) {
        if (this.style == "bar") {
          var ctx = this.chart.ctx;
          ctx.beginPath();

          for (t = 0; t < len; t++) {
            ctx.moveTo(center.x, center.y);
            ctx.lineTo(pp[t].x, pp[t].y);
          }

          this.format.stroke.prepare(this.format.fill, ctx);
          ctx.stroke();
        } else f.polygon(pp);

        if (this.pointer.visible) this.drawPointers();
      }
    };

    this.minYValue = function () {
      var v = Tee.Series.prototype.minYValue.call(this);
      return this.useOrigin ? Math.min(v, this.origin) : v;
    };

    this.maxYValue = function () {
      var v = this.stackMaxValue();
      return this.useOrigin ? Math.max(v, this.origin) : v;
    };
  };

  Tee.Polar.prototype = new Tee.CustomSeries();

  // String.trim:
  if (typeof String.prototype.trim !== "function") {
    String.prototype.trim = function () {
      return this.replace(/^\s+|\s+$/g, "");
    };
  }

  /**
   * @constructor
   * @augments Tee.Series
   * @class Calculates each point color using point values and the palette colors array.
   */
  Tee.PaletteSeries = function (o, o2) {
    Tee.Series.call(this, o, o2);

    var palette = this.palette;
    palette.colors = Tee.RainbowPalette();

    var rgb, numcolors;

    this._min = 0;
    this._range = 0;

    this.prepareColors = function () {
      var p = palette.colors,
        color;

      numcolors = p.length;
      rgb = new Array(numcolors);

      for (var c = 0; c < numcolors; c++) {
        color = p[c].trim();

        if (color.length == 7)
          // #RRGGBB
          rgb[c] = {
            r: parseInt(color.substr(1, 2), 16),
            g: parseInt(color.substr(3, 2), 16),
            b: parseInt(color.substr(5, 2), 16),
            a: 0,
          };
        else if (color.substr(0, 4) == "rgb(") {
          var tmp = color.slice(4, color.length - 1).split(",");
          rgb[c] = { r: tmp[0], g: tmp[1], b: tmp[2], a: tmp[3] || 0 };
        }
      }
    };

    this.getColor = function (value) {
      var colorIndex =
        ((numcolors - 1) * ((value - this._min) / this._range)) | 0;
      return rgb[palette.inverted ? numcolors - 1 - colorIndex : colorIndex];
    };

    /**
     * @returns {Number} Returns the number of items to show at legend.
     */
    this.legendCount = function () {
      return this.palette.colors ? this.palette.colors.length : 0;
    };
  };

  Tee.PaletteSeries.prototype = new Tee.Series();

  /**
   * @returns {String} Returns the color of index'th legend symbol.
   */
  Tee.PaletteSeries.prototype.legendColor = function (index) {
    var p = this.palette,
      c = p.colors,
      i = p.inverted;

    if (this.chart.legend.inverted) i = !i;

    if (p.grayScale) {
      var tmp = ((index * 255) / c.length) | 0;
      if (i) tmp = 255 - tmp;
      return "rgb(" + tmp + "," + tmp + "," + tmp + ")";
    } else
      return c
        ? c[i ? index : this.legendCount() - index - 1]
        : this.format.fill;
  };

  /**
   * @returns {String} Returns the palette value of index'th legend symbol.
   */
  Tee.PaletteSeries.prototype.legendText = function (
    index /*,style,title,asArray*/
  ) {
    index = -1 + this.legendCount() - index;
    return (
      this._min +
      (index * this._range) / (this.palette.colors.length - 1)
    ).toFixed(this.decimals);
  };

  // This script made by:
  // Michael Leigeber
  // http://www.scriptiny.com/
  // http://www.scriptiny.com/author/michael/

  Tee.DOMTip = (function () {
    var top = 3,
      left = 3,
      arrowtt,
      speed = 10,
      timer = 10,
      arrowWidth = 8,
      arrowStyleBefore,
      followCursor = false,
      arrowStyleAfter,
      arrowBorderWidth,
      tip,
      previousIndex,
      previousNearestSeries,
      domStylesBorderColor,
      domStylesBackgroundColor,
      arrowBorderRadius,
      endalpha = 97,
      alpha = 0,
      tt,
      ttstyle,
      h,
      animation,
      target,
      ie = typeof document !== "undefined" && document.all ? true : false,
      width;

    return {
      show: function (v, w, dest, domStyle, toolTip) {
        if (toolTip) {
          tip = toolTip;
          if (!toolTip.findPoint) {
            arrowWidth = 0;
            followCursor = true;
          }
        }
        if (!tt) {
          arrowStyleAfter = document.createElement("style");
          arrowStyleBefore = document.createElement("style");

          domStylesBorderColor = "";
          tt = document.createElement("div");
          arrowtt = document.createElement("div");

          tt.setAttribute("id", "teetip1");
          arrowtt.setAttribute("id", "teetiparrow1");
          tt.className = "teetip";
          arrowtt.className = "teetiparrow";

          tt.setAttribute("style", domStyle);
          domStylesBorderColor = tt.style.getPropertyValue("border-color");
          domStylesBackgroundColor =
            tt.style.getPropertyValue("background-color");

          arrowBorderWidth = tt.style.getPropertyValue("border-width");
          arrowBorderRadius = tt.style.getPropertyValue("border-radius");

          arrowBorderRadius = arrowBorderRadius.substring(
            0,
            arrowBorderRadius.length - 2
          );

          if (arrowBorderWidth.length == 0) arrowBorderWidth = 0;
          else {
            arrowBorderWidth = arrowBorderWidth.substring(
              0,
              arrowBorderWidth.length - 2
            );
            arrowBorderWidth = arrowBorderWidth * 2;
          }

          arrowStyleBefore.innerHTML =
            ".teetiparrow{width:0;height:0;border: " +
            arrowWidth +
            "px solid;position: absolute;content: '';border-color: " +
            domStylesBorderColor +
            " transparent transparent transparent;bottom: -" +
            arrowWidth * 2 +
            "px;left: 25px;}";
          arrowStyleAfter.innerHTML =
            ".teetiparrow:after{content: ' ';position: absolute;width: 0;height: 0;left: -" +
            (arrowWidth - arrowBorderWidth / 2) +
            "px;bottom: " +
            (arrowBorderWidth - (arrowWidth - 1)) +
            "px; border: " +
            (arrowWidth - arrowBorderWidth / 2) +
            "px solid;border-color: " +
            domStylesBackgroundColor +
            " transparent transparent transparent;}";
          document.head.appendChild(arrowStyleBefore);
          document.head.appendChild(arrowStyleAfter);

          document.body.appendChild(tt);
          tt.appendChild(arrowtt);
          ttstyle = tt.style;
          ttstyle.opacity = 0;

          // IE only:
          if (ie) ttstyle.filter = "alpha(opacity=0)";
        }

        target = dest;

        ttstyle.display = "block";
        ttstyle.position = "absolute";
        tt.innerHTML = arrowtt.outerHTML + v;
        ttstyle.width = w ? w + "px" : "auto";

        if (!w && ie) ttstyle.width = tt.offsetWidth;

        if (tt.offsetWidth > 300) ttstyle.width = 300 + "px";
        width = tt.offsetWidth;
        h = parseInt(tt.offsetHeight, 10) + top;
        if (tt.timer) clearInterval(tt.timer);
        tt.timer = setInterval(function () {
          Tee.DOMTip.fade(1);
        }, timer);

        document.onmousemove = this.pos;
      },

      pos: function (e) {
        if (tip) {
          if (!tip.findPoint) {
            arrowWidth = 0;
            followCursor = true;
          } else {
            arrowWidth = 8;
            followCursor = false;
          }

          if (target) {
            var chart = target.chart;
            var chartRect = chart.chartRect;
            var horizontal = chart.axes.bottom.firstSeries
              ? !chart.axes.bottom.firstSeries.yMandatory
              : false;
          }
          if (chart) {
            chart.draw();
            if (
              chart.series.items[0] instanceof Tee.Pie ||
              followCursor ||
              !target
            ) {
              var d = document.documentElement,
                u = ie ? e.clientY + d.scrollTop : e.pageY,
                l = ie
                  ? e.clientX + d.scrollLeft
                  : e.pageX - width / 2 - 10 - arrowWidth;
              arrowStyleBefore.innerHTML =
                ".teetiparrow{width:0;height:0;border: " +
                arrowWidth +
                "px solid;position: absolute;content: '';border-color: " +
                domStylesBorderColor +
                " " +
                domStylesBorderColor +
                " transparent transparent;bottom: -" +
                arrowWidth * 2 +
                "px;left: " +
                (tt.getBoundingClientRect().width -
                  arrowWidth * 2 -
                  arrowBorderWidth / 2) +
                "px;}";
              arrowStyleAfter.innerHTML =
                ".teetiparrow:after{content: ' ';position: absolute;width: 0;height: 0;left: -" +
                (arrowWidth - arrowBorderWidth / 2) +
                "px;bottom: " +
                (arrowBorderWidth - (arrowWidth - 1)) +
                "px; border: " +
                (arrowWidth - arrowBorderWidth / 2) +
                "px solid;border-color: " +
                domStylesBackgroundColor +
                " " +
                domStylesBackgroundColor +
                " transparent transparent;}";
            } else {
              var index;
              if (!horizontal)
                index = Math.round(
                  chart.axes.bottom.fromSizeCalcIndex(
                    e.clientX -
                      chart.canvas.getBoundingClientRect().left -
                      chart.axes.bottom.startPos
                  )
                );
              else index = Math.round(chart.axes.left.fromPos(e.layerY));
              var point = new Point();
              var distance, minDistance;
              if (e.target == chart.canvas) {
                for (var n = 0; n < chart.series.items.length; n++) {
                  if (!horizontal)
                    distance = Math.abs(
                      chart.series.items[n].data.values[index] -
                        chart.axes.left.fromPos(e.layerY)
                    );
                  else
                    distance = Math.abs(
                      chart.series.items[n].data.values[index] -
                        chart.axes.bottom.fromPos(e.layerX)
                    );
                  if (n == 0) {
                    minDistance = distance;
                    chart.series.items[n].calc(index, point);
                  } else if (distance < minDistance) {
                    chart.series.items[n].calc(index, point);
                    minDistance = distance;
                  }
                }
              }
              var d = document.documentElement;
              var marginTop = tt.style.marginTop.substring(
                0,
                tt.style.marginTop.length - 2
              );
              var marginLeft = tt.style.marginLeft.substring(
                0,
                tt.style.marginLeft.length - 2
              );
              var u = Math.round(
                point.y +
                  window.scrollY +
                  chart.canvas.getBoundingClientRect().top -
                  tt.getBoundingClientRect().height -
                  arrowWidth -
                  marginTop +
                  arrowBorderWidth / 2
              );
              var l = Math.round(
                point.x +
                  window.scrollX +
                  chart.canvas.getBoundingClientRect().left -
                  tt.getBoundingClientRect().width / 2 -
                  marginLeft -
                  arrowBorderWidth / 2
              );

              if (l + width / 2 - chartRect.x > chartRect.width) {
                u = Math.round(
                  point.y +
                    window.scrollY +
                    chart.canvas.getBoundingClientRect().top -
                    tt.getBoundingClientRect().height -
                    arrowWidth * 2 -
                    marginTop +
                    arrowBorderWidth / 2
                );
                l = Math.round(
                  point.x +
                    window.scrollX +
                    chart.canvas.getBoundingClientRect().left -
                    tt.getBoundingClientRect().width -
                    marginLeft
                );
                ttstyle.borderRadius =
                  arrowBorderRadius +
                  "px " +
                  arrowBorderRadius +
                  "px 0px " +
                  arrowBorderRadius +
                  "px";
                arrowStyleBefore.innerHTML =
                  ".teetiparrow{width:0;height:0;border: " +
                  arrowWidth +
                  "px solid;position: absolute;content: '';border-color: " +
                  domStylesBorderColor +
                  " " +
                  domStylesBorderColor +
                  " transparent transparent;bottom: -" +
                  arrowWidth * 2 +
                  "px;left: " +
                  (tt.getBoundingClientRect().width -
                    arrowWidth * 2 -
                    arrowBorderWidth / 2) +
                  "px;}";
                arrowStyleAfter.innerHTML =
                  ".teetiparrow:after{content: ' ';position: absolute;width: 0;height: 0;left: -" +
                  (arrowWidth - arrowBorderWidth / 2) +
                  "px;bottom: " +
                  (arrowBorderWidth - (arrowWidth - 1)) +
                  "px; border: " +
                  (arrowWidth - arrowBorderWidth / 2) +
                  "px solid;border-color: " +
                  domStylesBackgroundColor +
                  " " +
                  domStylesBackgroundColor +
                  " transparent transparent;}";
              } else if (l - width / 2 < chartRect.x) {
                l = Math.round(
                  point.x +
                    window.scrollX +
                    chart.canvas.getBoundingClientRect().left -
                    marginLeft
                );
                u = Math.round(
                  point.y +
                    window.scrollY +
                    chart.canvas.getBoundingClientRect().top -
                    tt.getBoundingClientRect().height -
                    arrowWidth * 2 -
                    marginTop +
                    arrowBorderWidth / 2
                );
                ttstyle.borderRadius =
                  arrowBorderRadius +
                  "px " +
                  arrowBorderRadius +
                  "px " +
                  arrowBorderRadius +
                  "px 0px";
                arrowStyleBefore.innerHTML =
                  ".teetiparrow{width:0;height:0;border: " +
                  arrowWidth +
                  "px solid;position: absolute;content: '';border-color: " +
                  domStylesBorderColor +
                  " transparent transparent " +
                  domStylesBorderColor +
                  ";bottom: -" +
                  arrowWidth * 2 +
                  "px;left: " +
                  (0 - arrowBorderWidth / 2) +
                  "px;}";
                arrowStyleAfter.innerHTML =
                  ".teetiparrow:after{content: ' ';position: absolute;width: 0;height: 0;left: -" +
                  (arrowWidth - arrowBorderWidth / 2) +
                  "px;bottom: " +
                  (arrowBorderWidth - (arrowWidth - 1)) +
                  "px; border: " +
                  (arrowWidth - arrowBorderWidth / 2) +
                  "px solid;border-color: " +
                  domStylesBackgroundColor +
                  " transparent transparent " +
                  domStylesBackgroundColor +
                  ";}";
              } else if (l) {
                ttstyle.borderRadius =
                  arrowBorderRadius +
                  "px " +
                  arrowBorderRadius +
                  "px " +
                  arrowBorderRadius +
                  "px " +
                  arrowBorderRadius +
                  "px";
                arrowStyleBefore.innerHTML =
                  ".teetiparrow{width:0;height:0;border: " +
                  arrowWidth +
                  "px solid;position: absolute;content: '';border-color: " +
                  domStylesBorderColor +
                  " transparent transparent transparent;bottom: -" +
                  arrowWidth * 2 +
                  "px;left: " +
                  (tt.getBoundingClientRect().width / 2 - arrowWidth) +
                  "px;}";
                arrowStyleAfter.innerHTML =
                  ".teetiparrow:after{content: ' ';position: absolute;width: 0;height: 0;left: -" +
                  (arrowWidth - arrowBorderWidth / 2) +
                  "px;bottom: " +
                  (arrowBorderWidth - (arrowWidth - 1)) +
                  "px; border: " +
                  (arrowWidth - arrowBorderWidth / 2) +
                  "px solid;border-color: " +
                  domStylesBackgroundColor +
                  " transparent transparent transparent;}";
              }
            }
            if (tip && tip.pointer.visible) {
              drawPoint();
              u -=
                tip.pointer.secondCircleRadius > tip.pointer.firstCircleRadius
                  ? tip.pointer.secondCircleRadius
                  : tip.pointer.firstCircleRadius;
            }
          } else {
            var d = document.documentElement,
              u = ie ? e.clientY + d.scrollTop : e.pageY,
              l = ie
                ? e.clientX + d.scrollLeft
                : e.pageX - width / 2 - 10 - arrowWidth;
          }
        } else {
          arrowWidth = 0;
          followCursor = true;
          var d = document.documentElement,
            u = ie ? e.clientY + d.scrollTop : e.pageY,
            l = ie
              ? e.clientX + d.scrollLeft
              : e.pageX - width / 2 - 10 - arrowWidth;
          arrowStyleBefore.innerHTML =
            ".teetiparrow{width:0;height:0;border: " +
            arrowWidth +
            "px solid;position: absolute;content: '';border-color: " +
            domStylesBorderColor +
            " " +
            domStylesBorderColor +
            " transparent transparent;bottom: -" +
            arrowWidth * 2 +
            "px;left: " +
            (tt.getBoundingClientRect().width -
              arrowWidth * 2 -
              arrowBorderWidth / 2) +
            "px;}";
          arrowStyleAfter.innerHTML =
            ".teetiparrow:after{content: ' ';position: absolute;width: 0;height: 0;left: -" +
            (arrowWidth - arrowBorderWidth / 2) +
            "px;bottom: " +
            (arrowBorderWidth - (arrowWidth - 1)) +
            "px; border: " +
            (arrowWidth - arrowBorderWidth / 2) +
            "px solid;border-color: " +
            domStylesBackgroundColor +
            " " +
            domStylesBackgroundColor +
            " transparent transparent;}";
        }
        if (u - h < 0) u = h;
        if (l < 0) l = 0;

        if (target) {
          var offsetLeft = target.offsetLeft;
          var node = target;
          while (node.offsetParent != null) {
            offsetLeft += node.offsetLeft;
            node = node.offsetParent;
          }

          if (l > offsetLeft + target.clientWidth - tt.offsetWidth - 25)
            l = offsetLeft + target.clientWidth - tt.offsetWidth - 25;
        }

        // Try to hide tooltip when moving mouse outside target bounds:

        /*
      if (target) {
        if ((l>target.clientLeft) && (l<(target.clientLeft+target.clientWidth)) )
        {
        }
        else {
          Tee.DOMTip.hide();
          return;
        }
      }
      */
        if (followCursor) {
          ttstyle.top = u - h + "px";
          ttstyle.left = l + left + "px";
        } else {
          ttstyle.top = u /*- h*/ + "px";
          ttstyle.left = l /*+ left*/ + "px";
        }

        function animatePointer(point, c) {
          var pointer = tip.pointer;
          animation = new Tee.Animation(tip, function (f) {
            if (f < 1) {
              tip.chart.draw();
              drawPointAt(
                point,
                pointer.secondCircleRadius * f,
                pointer.fill,
                pointer.secondCircleOpacity,
                c
              );
              drawPointAt(
                point,
                pointer.firstCircleRadius * f,
                pointer.fill,
                pointer.firstCircleOpacity,
                c
              );
            }
          });
          animation.onstop = function () {
            tip.chart.draw();
            drawPointAt(
              point,
              pointer.secondCircleRadius,
              pointer.fill,
              pointer.secondCircleOpacity,
              c
            );
            drawPointAt(
              point,
              pointer.firstCircleRadius,
              pointer.fill,
              pointer.firstCircleOpacity,
              c
            );
          };
          animation.duration = pointer.animationDuration;
          animation.animate();
        }
        function drawPointAt(point, size, color, opacity, c) {
          c.strokeStyle = color;
          c.fillStyle = color;
          c.globalAlpha = opacity;
          c.lineWidth = size;
          c.beginPath();
          c.ellipse(point.x, point.y, size / 2, size / 2, 0, 0, 7, false);
          c.stroke();
        }
        function drawPoint() {
          if (e.target.chart) {
            e.target.chart.draw();
            var index;
            var point = new Point();
            var distance, minDistance;
            var nearestSeries;
            var pointer = tip.pointer;
            var color = tip.pointer.fill;
            if (!horizontal)
              index = Math.round(
                chart.axes.bottom.fromSizeCalcIndex(
                  e.clientX -
                    chart.canvas.getBoundingClientRect().left -
                    chart.axes.bottom.startPos
                )
              );
            else index = Math.round(chart.axes.left.fromPos(e.layerY));
            if (e.target == chart.canvas) {
              for (var n = 0; n < chart.series.items.length; n++) {
                if (!horizontal)
                  distance = Math.abs(
                    chart.series.items[n].data.values[index] -
                      chart.axes.left.fromPos(e.layerY)
                  );
                else
                  distance = Math.abs(
                    chart.series.items[n].data.values[index] -
                      chart.axes.bottom.fromPos(e.layerX)
                  );
                if (n == 0) {
                  minDistance = distance;
                  chart.series.items[n].calc(index, point);
                  nearestSeries = chart.series.items[n];
                } else if (distance < minDistance) {
                  chart.series.items[n].calc(index, point);
                  minDistance = distance;
                  nearestSeries = chart.series.items[n];
                }
              }
            }

            var c = e.target.chart.ctx;
            if (!previousNearestSeries) {
              previousNearestSeries = nearestSeries;
            } else if (index != -1 && previousNearestSeries != nearestSeries) {
              previousNearestSeries = nearestSeries;
              if (pointer.animationVisible) animatePointer(point, c); //here will go the animation
            }

            if ((index != -1 && !previousIndex) || index == -1) {
              previousIndex = index;
            } else if (index != -1 && previousIndex != index) {
              previousIndex = index;
              if (pointer.animationVisible) animatePointer(point, c); //here will go the animation
            }

            drawPointAt(
              point,
              pointer.secondCircleRadius,
              pointer.fill,
              pointer.secondCircleOpacity,
              c
            );
            drawPointAt(
              point,
              pointer.firstCircleRadius,
              pointer.fill,
              pointer.firstCircleOpacity,
              c
            );
          }
        }
      },

      fade: function (d) {
        var a = alpha;

        if ((a !== endalpha && d === 1) || (a !== 0 && d === -1)) {
          var i = speed;

          if (endalpha - a < speed && d == 1) i = endalpha - a;
          else if (alpha < speed && d == -1) i = a;

          alpha = a + i * d;

          ttstyle.opacity = alpha * 0.01;

          // IE only:
          if (ie) ttstyle.filter = "alpha(opacity=" + alpha + ")";
        } else {
          clearInterval(tt.timer);
          if (d == -1) {
            ttstyle.display = "none";
            document.onmousemove = null;
          }
        }
      },

      hide: function () {
        if (tt) {
          clearInterval(tt.timer);
          tt.timer = setInterval(function () {
            Tee.DOMTip.fade(-1);
          }, timer);
          if (target && target.chart) target.chart.draw();
        }
      },
    };
  })();
  
//*********** extras start ******************
Tee.Chart.prototype.drawReflection=function()
{
  var c=this.ctx, h=this.bounds.height;

  c.scale(1,-1);
  c.translate(0,-h*2);

  this.ondraw=null;
  this.draw();
  c.translate(0,h*2);
  c.scale(1,-1);

  var mirrorHeight=this.canvas.height-h, y=h,
      gradient = c.createLinearGradient( 0, y, 0, y+mirrorHeight),
      color=this.reflectionColor;

  gradient.addColorStop( 0, colorAlpha(color,0.5));
  gradient.addColorStop( 1, colorAlpha(color,1));
  c.fillStyle = gradient;
  c.beginPath();
  c.shadowColor="transparent";
  c.rect( 0, y, this.bounds.width, mirrorHeight );
  c.fill();

  this.ondraw=this.drawReflection;
}

function colorAlpha(color,alpha) {
  return 'rgba( '+color[0]+', '+color[1]+', '+color[2]+', '+alpha+' )';
}


/*
  Copyright 2010 by Robin W. Spencer
  http://scaledinnovation.com/analytics/splines/aboutSplines.html

  Modifications by Steema Software.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You can find a copy of the GNU General Public License
    at http://www.gnu.org/licenses/.
*/

Tee.drawSpline=function(ctx,pts,t,move,closed){
  var cp=[], n=pts.length, i;

  function point(x0,y0,x1,y1,x2,y2,t){

      function square(x) { return x*x; }

      var d=Math.sqrt(square(x1-x0)+square(y1-y0)),
          a=t*d/(d+Math.sqrt(square(x2-x1)+square(y2-y1))),
          b=t-a;

      return [x1+a*(x0-x2),y1+a*(y0-y2),x1-b*(x0-x2),y1-b*(y0-y2)];
  }

  if(closed){
    if (move)
       ctx.moveTo(pts[0],pts[1]);

    pts.push(pts[0],pts[1],pts[2],pts[3]);
    pts.unshift(pts[n-1]);
    pts.unshift(pts[n-1]);

    for(i=0;i<n;i+=2)
        cp=cp.concat(point(pts[i],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));

    cp=cp.concat(cp[0],cp[1]);

    for(i=2;i<n+2;i+=2)
        ctx.bezierCurveTo(cp[2*i-2],cp[2*i-1],cp[2*i],cp[2*i+1],pts[i+2],pts[i+3]);
  }
  else
  {
    for(i=0;i<n-4;i+=2)
        cp=cp.concat(point(pts[i],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));

    if (move)
       ctx.moveTo(pts[0],pts[1]);

    ctx.quadraticCurveTo(cp[0],cp[1],pts[2],pts[3]);

    for(i=2;i<n-5;i+=2)
        ctx.bezierCurveTo(cp[2*i-2],cp[2*i-1],cp[2*i],cp[2*i+1],pts[i+2],pts[i+3]);

    ctx.quadraticCurveTo(cp[2*n-10],cp[2*n-9],pts[n-2],pts[n-1]);
  }
}

function modCustomAxes(c,featureColor,defaultStrokeColor) {
  for (var i = 0; i < c.axes.items.length; i++) {
	  if (i>3){
		c.axes.items[i].labels.format.font.setSize(11);
		c.axes.items[i].format.stroke.fill = defaultStrokeColor;
		c.axes.items[i].labels.format.font.fill = featureColor;
		c.axes.items[i].title.format.font.fill = featureColor;
		c.axes.items[i].title.format.font.setSize(20);
		c.axes.items[i].grid.visible=false;
  	    c.axes.items[i].grid.format.stroke.size = 0.6;
	    c.axes.items[i].grid.format.stroke.fill = "silver";
	  }
	}
}

function defaultTheme(c) {

  var featureColor = "rgba(0,0,0,1)";
  var defaultStrokeColor = "rgba(39,79,105,0.8)";
  var backBlendColor = "white";
  var seriesPenColor = "white";
  
  c.title.format.font.style="18px Verdana";
  c.walls.visible=true;
  c.panel.format.shadow.visible=false;
  c.panel.format.round.x=8;
  c.panel.format.round.y=8;
  c.panel.format.gradient.visible=true;
  c.panel.format.gradient.colors=["rgba(224,224,224,1.0)","white"];
  c.panel.format.gradient.direction="diagonalup";
  c.panel.format.stroke.fill="rgba(204,204,204,1.0)";
  c.panel.format.stroke.size = 1;
  
  applyPalette(c,"opera");
  
  if (c.series.items.length > 0) {
  	for (var i = 0; i < c.series.items.length; i++)
	  {
			c.series.items[i].format.fill=c.palette.get(i);
			if (c.series.items[i].pointer != null)
			{
				c.series.items[i].pointer.format.fill=c.palette.get(i);
				c.series.items[i].pointer.format.stroke.fill = backBlendColor;
			}
	  }
  }  
  
  c.axes.left.labels.format.font.setSize(11);
  c.axes.bottom.labels.format.font.setSize(11);
  c.axes.left.format.stroke.fill = defaultStrokeColor;
  c.axes.bottom.format.stroke.fill = defaultStrokeColor;
  c.axes.left.labels.format.font.fill = featureColor;
  c.axes.bottom.labels.format.font.fill = featureColor;
  c.axes.left.title.format.font.fill = featureColor;
  c.axes.left.title.format.font.setSize(20);  
  c.axes.bottom.title.format.font.fill = featureColor;
  c.axes.bottom.title.format.font.setSize(20);
  c.axes.left.grid.visible=true;
  c.axes.bottom.grid.visible=false;

  c.axes.left.grid.format.stroke.size = 0.6;
  c.axes.bottom.grid.format.stroke.size = 0.6;
  c.axes.left.grid.format.stroke.fill = "silver";
  c.axes.bottom.grid.format.stroke.fill = "silver";

  c.axes.left.grid.visible=true;
  c.axes.top.grid.visible=true;
  c.axes.right.grid.visible=true;
  c.axes.bottom.grid.visible=true;

  if (c.axes.items.length > 0) {
    modCustomAxes(c,featureColor,defaultStrokeColor)
  }

  //legend
  c.legend.transparent=false;
  c.legend.format.fill = "white";
  c.legend.format.font.setSize(11);
  c.legend.format.font.fill = featureColor;  
  c.legend.fontColor = false;  
  
  //title
  c.title.format.font.fill = featureColor;
  
  c.walls.visible = false;  
}

function twilightTheme(c) {

  var featureColor = "rgba(224,224,224,0.6)";
  var defaultStrokeColor = "rgba(39,79,105,0.8)";
  var backBlendColor = "rgba(82,82,82,1)";
  var seriesPenColor = "white";
  
  c.title.format.font.style="18px Verdana";
  c.walls.visible=true;
  c.panel.format.shadow.visible=false;
  c.panel.format.round.x=8;
  c.panel.format.round.y=8;
  c.panel.format.gradient.visible=true;
  c.panel.format.gradient.colors=["rgba(99,99,99,1.0)","rgba(19,19,19,1.0)"];
  c.panel.format.gradient.direction="topbottom";
  c.panel.format.stroke.fill="rgba(204,204,204,1.0)";
  c.panel.format.stroke.size = 1;
  
  applyPalette(c,"redRiver");
  
  if (c.series.items.length > 0) {
  	for (var i = 0; i < c.series.items.length; i++)
	  {
			c.series.items[i].format.fill=c.palette.get(i);
			if (c.series.items[i].pointer != null)
			{
				c.series.items[i].pointer.format.fill=c.palette.get(i);
				c.series.items[i].pointer.format.stroke.fill = backBlendColor;
			}
	  }
  }
  
  //axes
  c.axes.left.format.stroke.fill = featureColor;
  c.axes.bottom.format.stroke.fill = featureColor;
  c.axes.left.labels.format.font.setSize(11);
  c.axes.bottom.labels.format.font.setSize(11);
  c.axes.left.labels.format.font.fill = featureColor;
  c.axes.bottom.labels.format.font.fill = featureColor;
  c.axes.left.title.format.font.fill = featureColor;
  c.axes.left.title.format.font.setSize(20);  
  c.axes.bottom.title.format.font.fill = featureColor;
  c.axes.bottom.title.format.font.setSize(20);
  c.axes.bottom.grid.visible=false;
  c.axes.left.grid.visible=true; 
  c.axes.left.grid.format.stroke.fill = "silver";
  c.axes.bottom.grid.format.stroke.fill = "silver";
  
  if (c.axes.items.length > 0) {
    modCustomAxes(c,featureColor,defaultStrokeColor)
  }
  
  //legend
  c.legend.transparent=true;
  c.legend.format.font.setSize(14);
  c.legend.format.font.fill = featureColor;
  c.legend.format.fill = "rgba(0,0,0,0.1)";
  
  //title
  c.title.format.shadow.visible=false;
  var baseFontStyle = "18px Arial";
  c.title.format.font.style=baseFontStyle;
  c.title.format.font.style="bold " + baseFontStyle;
  c.title.format.font.fill = featureColor;
  c.title.format.font.shadow.visible=false;    
  
  c.walls.visible = false;  
}

function daybreakTheme(c) {

  var darkContrastColor = "rgba(14,14,54,0.6)";
  var featureColor = "rgba(224,224,224,0.6)";
  var defaultStrokeColor = "rgba(39,79,105,0.8)";
  var backBlendColor = "rgba(82,82,82,1)";
  var seriesPenColor = "white";
  
  c.title.format.font.style="18px Verdana";
  c.walls.visible=true;
  c.panel.format.shadow.visible=false;
  c.panel.format.round.x=8;
  c.panel.format.round.y=8;
  c.panel.format.gradient.visible=true;
  c.panel.format.gradient.colors=["rgba(201,204,242,1.0)","rgba(255,252,255,1.0)","rgba(21,21,23,1.0)"];
  c.panel.format.gradient.direction="topbottom";
  c.panel.format.stroke.fill="rgba(204,204,204,1.0)";
  c.panel.format.stroke.size = 1;
  
  applyPalette(c,"redRiver");
  
  if (c.series.items.length > 0) {
  	for (var i = 0; i < c.series.items.length; i++)
	  {
			c.series.items[i].format.fill=c.palette.get(i);
			if (c.series.items[i].pointer != null)
			{
				c.series.items[i].pointer.format.fill=c.palette.get(i);
				c.series.items[i].pointer.format.stroke.fill = backBlendColor;
			}
	  }
  }

  //axes
  for (var i=0; i<c.axes.items.length; i++) {
    var a = c.axes.items[i];
    a.format.stroke.fill = darkContrastColor;
    a.labels.format.font.setSize(11);
    a.labels.format.font.fill = featureColor;
    a.title.format.font.setSize(20);
    a.title.format.font.fill = featureColor;
    a.grid.visible=i<3;
    a.grid.format.stroke.fill = "silver";
  }
  
  //legend
  c.legend.transparent=true;
  c.legend.format.font.setSize(14);
  c.legend.format.font.fill = "silver";
  
  //title
  c.title.format.shadow.visible=false;
  var baseFontStyle = "18px Arial";
  c.title.format.font.style=baseFontStyle;
  c.title.format.font.style="bold " + baseFontStyle;
  c.title.format.font.fill = darkContrastColor;
  c.title.format.font.shadow.visible=false;  
  
  c.walls.visible = false;  
}

function minimalTheme(c) {
  //designed with white background in mind
  c.title.transparent=true;
  c.walls.visible=false;
  c.footer.transparent=true;
  c.panel.format.shadow.visible=false;
  c.panel.format.stroke.fill="";
  c.panel.format.round.x=0;
  c.panel.format.round.y=0;
  c.panel.format.gradient.visible=false;
  c.panel.format.fill="white";
  
  var featureColor = "rgba(124,124,144,0.9)";
  var defaultStrokeColor = "rgba(39,79,105,0.8)";
  var invisibleStrokeColor = "rgba(0,0,0,0.0)";
  var backBlendColor = "white";
  var seriesPenColor = "white";
  
  applyPalette(c,"seaWash");
  
  if (c.series.items.length > 0) {
      for (var i = 0; i < c.series.items.length; i++) {
          c.series.items[i].format.fill=c.palette.get(i);
          if ((c.series.items[i].pointer != null) && (c.series.items[i].pointer.format != null)) {
              c.series.items[i].pointer.format.fill=c.palette.get(i);
              c.series.items[i].pointer.format.stroke.fill = backBlendColor;
          }
      }
  }
  
  //axes
  for (var i=0; i<c.axes.items.length; i++) {
    var a = c.axes.items[i];
    a.format.stroke.fill = defaultStrokeColor;
    a.labels.format.font.setSize(14);
    a.labels.format.font.fill = featureColor;
    a.title.format.font.setSize(20);
    a.title.format.font.fill = featureColor;
  }

  /*c.series.each(function(series) {
    series.notmandatory.grid.visible=false;
  });*/  
  
  for (i = 0; i < c.axes.items.length; i++) {
			  
    if (!c.axes.items[i].horizontal)
	{
		c.axes.items[i].grid.visible=true;
		c.axes.items[i].grid.format.stroke.size = 0.6;
		c.axes.items[i].grid.format.stroke.fill = "silver";

	}
	else
	{
	  c.axes.items[i].grid.visible=false;
	  c.axes.items[i].grid.format.stroke.size = 0.6;
	  c.axes.items[i].grid.format.stroke.fill = "silver";
	}
  }
  
  
  /*if (c.axes.items.length > 0) {
    modCustomAxes(c,featureColor,defaultStrokeColor)
  }*/

  //legend
  c.legend.transparent=true;
  c.legend.format.font.setSize(14);
  c.legend.format.font.fill = featureColor;
  
  //title
  c.title.format.shadow.visible=false;
  var baseFontStyle = "18px Arial";
  c.title.format.font.style=baseFontStyle;
  c.title.format.font.style="bold " + baseFontStyle;
  c.title.format.font.fill = featureColor;
  c.title.format.font.shadow.visible=false;

}

function excelTheme(c) {

  minimalTheme(c);
 
  var featureColor = "rgba(0,0,0,0.9)";
  var defaultStrokeColor = "rgba(39,79,105,0.8)";
  var backBlendColor = "white";
  var seriesPenColor = "white";
  
  applyPalette(c,"excel");
  
  c.axes.left.grid.format.stroke.fill = featureColor;
  c.axes.bottom.grid.format.stroke.fill = featureColor;
  
  if (c.series.items.length > 0) {
	  for (var i = 0; i < c.series.items.length; i++)
	  {
			c.series.items[i].format.fill=c.palette.get(i);
			if (c.series.items[i].pointer != null)
			{
				c.series.items[i].pointer.format.fill=c.palette.get(i);
				c.series.items[i].pointer.format.stroke.fill = backBlendColor;
			}
	  }
  }
}

function darkTheme(c) {
  
  applyPalette(c,"onBlack");
    
  var featureColor = "rgba(224,224,224,0.6)";
  var defaultStrokeColor = "rgba(39,79,105,0.8)";
  var backBlendColor = "rgba(82,82,82,1)";
  var seriesPenColor = "white";
  
  c.title.transparent=true;
  c.legend.transparent=true;
  c.footer.transparent=true;
  
  //panel
  c.panel.format.shadow.visible=false;
  c.panel.format.stroke.fill="";
  c.panel.format.round.x=0;
  c.panel.format.round.y=0;
  c.panel.format.gradient.colors=["rgba(0,0,0,1)","rgba(0,0,0,1)"];
  c.panel.format.gradient.visible=true;

  if (c.series.items.length > 0) {
  	for (var i = 0; i < c.series.items.length; i++)
	  {
			c.series.items[i].format.fill=c.palette.get(i);
			if (c.series.items[i].pointer != null)
			{
				c.series.items[i].pointer.format.fill=c.palette.get(i);
				c.series.items[i].pointer.format.stroke.fill = backBlendColor;
			}
	  }
  }
  
  //axes
  c.axes.left.format.stroke.fill = featureColor; //defaultStrokeColor;
  c.axes.bottom.format.stroke.fill = featureColor; //defaultStrokeColor;
  c.axes.left.labels.format.font.setSize(14);
  c.axes.bottom.labels.format.font.setSize(14);
  c.axes.left.labels.format.font.fill = featureColor;
  c.axes.bottom.labels.format.font.fill = featureColor;
  c.axes.left.title.format.font.fill = featureColor;
  c.axes.left.title.format.font.setSize(20);  
  c.axes.bottom.title.format.font.fill = featureColor;
  c.axes.bottom.title.format.font.setSize(20);
  c.axes.bottom.grid.visible=false;
  c.axes.left.grid.visible=true;
  c.axes.left.grid.format.stroke.fill = "silver";
  c.axes.bottom.grid.format.stroke.fill = "silver";
  
  if (c.axes.items.length > 0) {
    modCustomAxes(c,featureColor,defaultStrokeColor)
  }
  
  //walls
  c.walls.visible=false;

  //legend
  c.legend.transparent=true;
  c.legend.format.font.setSize(14);
  c.legend.format.font.fill = featureColor;
  
  //title
  c.title.format.shadow.visible=false;
  var baseFontStyle = "18px Arial";
  c.title.format.font.style=baseFontStyle;
  c.title.format.font.style="bold " + baseFontStyle;
  c.title.format.font.fill = featureColor;
  c.title.format.font.shadow.visible=false;
}

Tee.Chart.prototype.applyTheme=function(theme) {
  if ((!theme) || (theme==""))
    this.applyTheme("default");
  else
  if (theme=="default")
     defaultTheme(this);
  else
  if (theme=="minimal")
     minimalTheme(this);
  else
  if (theme=="excel")
     excelTheme(this);
  else
  if (theme=="dark")
    darkTheme(this);
  else
  if (theme=="twilight")  	
	twilightTheme(this);
  else
  if (theme=="daybreak")  	
	daybreakTheme(this);	

  this.themeName = theme;	
  this.draw();
}

Tee.Chart.prototype.applyPalette=function(paletteName) {
  applyPalette(this,paletteName);
}

function applyPalette(c,paletteName) {
 
  //default (Opera)
  var colorList = [ "#4466a3", "#f39c35", "#f14c14", "#4e97a8", "#2b406b",
                    "#1d7b63", "#b3080e", "#f2c05d", "#5db79e", "#707070",
                    "#f3ea8d", "#b4b4b4"];

  /*Castaway*/					 
  if (paletteName=="castaway")
    colorList = ["#4466a3", "#E8D0A9","#B7AFA3","#C1DAD6","#F5FAFA","#ACD1E9","#6D929B"];
  else /*ClassicPalette*/
  if (paletteName=="classic")	
	colorList = ["#0000FF","#00FF00","#00FFFF","#FF0000","#FF00FF","#FFFF00","#000080",
	             "#008000","#008080","#800000","#808000","#808080"];				   
  else /*Cool*/				
	if (paletteName=="cool")
		colorList=["rgba(43,64,107,1.0)","rgba(59,84,140,1.0)","rgba(68,102,163,1.0)",
		           "rgba(78,151,168,1.0)","rgba(93,183,158,1.0)","rgba(65,160,138,1.0)",
				   "rgba(43,146,125,1.0)","rgba(29,123,99)"];				 
  else /*Excel*/
  if (paletteName=="excel")
     colorList = ["#FF9999","#663399","#CCFFFF","#FFFFCC","#660066","#8080FF","#CC6600",
                  "#FFCCCC","#800000","#FF00FF","#00FFFF","#FFFF00","#800080","#000080",
				  "#808000","#FF0000","#FFCC00","#FFFFCC","#CCFFCC","#00FFFF","#FFCC99",
				  "#CC99FF"];
  else /*GrayscalePalette*/ 
  if (paletteName=="grayscale")	
	colorList = ["#F0F0F0","#E0E0E0","#D0D0D0","#C0C0C0","#B0B0B0","#A0A0A0","#909090",
	             "#808080","#707070","#606060","#505050","#404040","#303030","#202020",
				 "#101010"];	
  else /*MacOSPalette*/ 
  if (paletteName=="macOS")	
	colorList = ["#FFFFFF","#FCF305","#FF6402","#DD0806","#F20884","#4600A5","#0000D4",
	             "#02ABEA","#1FB714","#006411","#562C05","#90713A","#C0C0C0","#808080",
				 "#404040","#000000"];	
  else /*ModernPalette*/ 
  if (paletteName=="modern")	
	colorList = ["#FF9966","#FF6666","#99CCFF","#669966","#CCCC99","#9966CC","#CC6666",
	             "#FFCC99","#9966FF","#CCCCCC","#66FFCC","#6699FF","#996699","#CCCCFF"];				 
  else /*OnBlack*/
	if (paletteName=="onBlack")
		colorList=["rgba(200,230,90,1.0)","rgba(90,150,220,1.0)","rgba(230,90,40,1.0)",
		           "rgba(230,160,15)"];
  else /*Opera - default*/
  if (paletteName=="opera")
	 colorList = colorList; //do nothing, Opera is default.
  else /*PastelsPalette*/
  if (paletteName=="pastels")	
	colorList = ["#CCFFFF","#FFFFCC","#CCCCFF","#00CCCC","#CCCCCC","#009999","#999999",
	             "#DDCCCC","#FFCC66","#CCCCFF","#FF9999","#FFFF99","#99CCFF","#CCFFCC"];	 
  else /*Rainbow*/
  if (paletteName=="rainbow")	
	colorList = ["#FF0000","#FF7F00","#FFFF00","#00FF00","#0000FF","#6600FF","#8B00FF"]
  else /*RedRiver*/
  if (paletteName=="redRiver")
    colorList = ["#DC5C05","#FFC519","#6EC5B8","#FF9000","#978B7D","#C7BAA7"];  //#FFAC00
  else /*Rust*/
  if (paletteName=="rust")	
	colorList = ["#CBFFFA","#7F3D17","#7F5E17","#22287F","#DD1E2F","#EBB035","#06A2CB",
	             "#218559","#D0C6B1","#B67721","#68819E","#747E80","#D5E1DD","#F7F3E8",
				 "#F2583E","#77BED2"];
  else /*SeaWash*/
  if (paletteName=="seaWash")
    colorList = ["#DC5C05","#FFAC00","#6EC5B8","#E8D0A9","#978B7D","#C7BAA7","#C1DAD6",
	             "#FFC99F","#ACD1E9","#6D929B","#D3E397","#FFF5C3"];
  else /*SolidPalette*/ 
  if (paletteName=="solid")	
	colorList = ["#0000FF","#FF0000","#00FF00","#FFCC00","#404040","#FFFF00","#FF00C0",
	             "#FFFFFF"];
  else /*TeeChart*/
  if (paletteName=="teechart")
	colorList=["rgba(255,0,0,1.0)","rgba(0,128,0,1.0)","rgba(255,255,0,1.0)","rgba(0,0,255,1.0)",
	           "rgba(255,255,255,1.0)","rgba(128,128,128,1.0)","rgba(255,0,255,1.0)",
			   "rgba(0,128,128,1.0)","rgba(0,0,128,1.0)","rgba(128,0,0,1.0)","rgba(0,255,0,1.0)",
			   "rgba(128,128,0,1.0)","rgba(128,0,128,1.0)","rgba(192,192,192,1.0)",
			   "rgba(0,255,255,1.0)","rgba(0,0,0,1.0)","rgba(173,255,47,1.0)","rgba(135,206,235,1.0)",
			   "rgba(255,228,196,1.0)","rgba(75,0,130,1.0)"];
  else /*Warm*/
  if (paletteName=="warm")
	colorList = ["rgba(243,234,141,1.0)","rgba(242,192,93,1.0)","rgba(243,156,53,1.0)",
	             "rgba(245,129,28,1.0)","rgba(243,107,21,1.0)","rgba(241,76,20,1.0)",
				 "rgba(230,24,10,1.0)","rgba(179,8,14)"];
  else /*WebPalette*/ 
  if (paletteName=="web")	
	colorList = ["#FFA500","#0000CE","#00CE00","#FFFF40","#40FFFF","#FF40FF","#FF4000",
	             "#8080A5","#808040"];				 
  else /*RainbowWidePalette*/ 
  if (paletteName=="rainbowWide")	
	colorList = ["#990000","#C30000","#EE0000","#FF1A00","#FF4600","#FF7300","#FF9F00",
	             "#FFCB00","#FFF700","#E3F408","#C3E711","#A3DA1B","#83CD25","#63C02E",
				 "#42B338","#22A642","#029A4B","#0C876A","#1A758A","#2863AA","#3650CB",
				 "#443EEB","#612AFF","#9615FF","#CC00FF"];				 
  else /*WindowsVistaPalette*/
  if (paletteName=="windowsVista")	
	colorList = ["#001FD2","#E00201","#1E6602","#E8CD7E","#AFABAC","#A4D0D9","#3D3B3C",
	             "#95DD31","#9E0001","#DCF774","#45FDFD","#D18E74","#A0D891","#D57A65",
				 "#9695D9"];
  else /*WindowsXPPalette*/ 
  if (paletteName=="windowsxp")	
	colorList = ["rgba(130,155,254,1.0)","rgba(252,209,36,1.0)","rgba(124,188,13,1.0)","rgba(253,133,47,1.0)",
				 "rgba(253,254,252,1.0)","rgba(226,78,33,1.0)","rgba(41,56,214,1.0)","rgba(183,148,0,1.0)",
				 "rgba(90,134,0,1.0)","rgba(210,70,0,1.0)","rgba(211,229,250,1.0)","rgba(216,216,216,1.0)",
				 "rgba(95,113,123,1.0)"];
  else /*VictorianPalette*/
  if (paletteName=="victorian")	
	colorList = ["#5DA5A1","#C45331","#E79609","#F6E84A","#B1A2A7","#C9A784","#8C7951",
	             "#D8CDB7","#086553","#F7D87B","#016484"];				 
	
				 
  c.paletteName = paletteName;				 
  c.palette.colors = colorList;
    
  if (c.series.items.length > 0) {
	  for (var i = 0; i < c.series.items.length; i++)
	  {
		c.series.items[i].format.fill=c.palette.get(i);
		if ((c.series.items[i].pointer != null) && (c.series.items[i].pointer.format != null))
		{
		  c.series.items[i].pointer.format.fill=c.palette.get(i);
		}
	  }
  }	
  
  c.draw();
}

function makeHttpObject() {
  try {return new XMLHttpRequest();}
  catch (error) {}
  try {return new ActiveXObject("Msxml2.XMLHTTP");}
  catch (error) {}
  try {return new ActiveXObject("Microsoft.XMLHTTP");}
  catch (error) {}

  throw new Error("Could not create HTTP request object.");
}

Tee.doHttpRequest=function(target, url, success, failure) {
  var request = makeHttpObject();
  if (request) {
    request.onreadystatechange = function() {
      if (request.readyState == 4) {
        if ( (request.status === 200) || (request.status === 0) )
          success(target, request.responseText);
        else
        if (failure)
          failure(request.status, request.statusText);
      }
    };
    request.open("GET", url, true);
    request.send(null);
  }
}

Tee.Slider=function(chart,position) {
  Tee.Tool.call(this,chart);

  var touchDragging=false;
  
  var t=this.thumb=new Tee.Format(chart);
  t.round={ x:4, y:4}
  t.stroke.size=0.5;
  t.gradient.visible=false;
  t.gradient.direction="leftright";
  t.shadow.visible=false;

  var f=this.back=new Tee.Format(chart);
  f.fill="white";
  f.gradient.visible=false;
  f.stroke.fill="darkgrey";
  f.round={ x:4, y:4 }

  var g=this.grip=new Tee.Format(chart);
  g.round={ x:4, y:4}
  g.stroke.fill="rgb(20,20,20,1.0)";

  this.gripSize=3;

  var b=this.bounds={x:10, y:10, width:200, height:20};
  this.transparent=false;

  this.margin=16; // %
  this.min=0;
  this.max=100;

  this.position=(typeof position == 'undefined') ? 50 : position;

  this.useRange=false;
  this.thumbSize=8;
  this.horizontal=true;
  this.cursor="pointer";
  this.delta=0;

  function contains(r,p) {
    return (p.x>=r.x) && (p.x<=(r.x+r.width)) && (p.y>=r.y) && (p.y<=(r.y+r.height));
  }

  this.thumbRect=function(r) {
    var range=this.max-this.min,
        v=range > 0 ? (this.position-this.min)/range : 0;

    if (this.horizontal) {
       r.width=this.thumbSize;
       r.x=b.x+v*b.width-(r.width*0.5);
       r.y=b.y;
       r.height=b.height;
    }
    else
    {
      r.height=this.thumbSize;
      r.y=b.y+v*b.height-(r.height*0.5);
      r.x=b.x;
      r.width=b.width;
    }
  }

  var r={};

  this.gripRect=function(r) {
    if (this.horizontal) {
      var h=r.height*0.2;
      return {x:r.x-this.gripSize,y:(r.y+r.height*0.5)-h,width:2*this.gripSize,height:2*h};
    }
    else
    {
      var w=r.width*0.2;
      return {x:(r.x+r.width*0.5)-w,y:r.y-this.gripSize,width:2*w,height:2*this.gripSize};
    }
  }

  this.draw=function() {
    var d=this.horizontal ? b.height : b.width,
        m=d*this.margin*0.01;

    if (!this.transparent) {
       if (this.horizontal)
         this.back.rectangle(b.x,b.y+m,b.width,d-2*m);
       else
         this.back.rectangle(b.x+m,b.y,d-2*m,b.height);
    }

    if (this.onDrawThumb)
      this.onDrawThumb(this);

    this.thumbRect(r);

    if (this.invertThumb) {
      var th=this.thumb;

      if (this.horizontal) {
         th.rectangle(b.x,b.y+m,r.x,b.height-2*m);
         th.rectangle(b.x+r.x+r.width,b.y+m,b.width,b.height-2*m);
      }
      else
      {
         th.rectangle(b.x+m,b.y,b.width-2*m,r.y);
         th.rectangle(b.x+m,b.y+r.y+r.height,b.width-2*m,b.height);
      }
    }
    else
      this.thumb.rectangle(r);

    if (this.useRange) {
      if (this.horizontal) {
         var r1=this.gripRect(r);
         this.grip.rectangle(r1);
         r1.x+=r.width;
         this.grip.rectangle(r1);
      }
    }
  }

  this.clickAt=function(pos) {
    var off=this.horizontal ? b.x : b.y,
        s=this.horizontal ? b.width : b.height,
        ra=(this.max-this.min),
        v;

    v=this.min+Math.max(0,(pos+this.delta-off)*ra/s);

    if (v>this.max) v=this.max;

    if (this.onChanging) {
       var v2=this.onChanging(this,v);
       if (typeof v2 !== 'undefined')
          v=v2;
    }

    if (v<this.min) v=this.min; else if (v>this.max) v=this.max;

    this.chart.newCursor=this.cursor;

    if (this.position!=v)  {
      this.position=v;

      //requestAnimFrame(function() { this.chart.draw(); });

      this.chart.draw();
    }
  }

  this.resized=function() {
     if (this.onChanging) this.onChanging(this,this.position);
     this.chart.draw();
     this.chart.newCursor="col-resize";
  }
  this.chart.canvas.addEventListener('touchstart', function(e){
  	touchDragging=true;
  	e.preventDefault();
  });
  this.chart.canvas.addEventListener('touchend', function(e){
  	touchDragging=false;
  	e.preventDefault();
  });
  this.mousemove=function(p) {
    var s=this.horizontal ? b.width : b.height,
        pp=this.horizontal ? p.x : p.y,
        ra=(this.max-this.min);

    this.thumbRect(r);

    if (this.resizeBegin && (pp<(r.x+r.width))) {
      var old=this.thumbSize, dif=r.x-pp, v2=0.5*(dif*ra/s);
      this.thumbSize+=dif;
      this.position-=v2;

      if (this.position<this.min) {
        this.position=this.min;
        this.thumbSize=old;
      }

      this.resized();
    }
    else
    if (this.resizeEnd && (pp>r.x)) {
      var dif2=r.x+r.width-pp, v3=0.5*(dif2*ra/s);
      this.thumbSize -= dif2;
      this.position -= v3;
      this.resized();
    }
    else
    if (this.dragging||touchDragging) {
      this.clickAt(pp);
    }
    else
    {
      var ingrip=false;

      if (this.useRange) {
         var r1=this.gripRect(r);
         ingrip=contains(r1,p);
         if (!ingrip) {
           r1.x+=r.width;
           ingrip=contains(r1,p);
         }
      }

      if (ingrip)
         this.chart.newCursor="col-resize";
      else
      if (contains(r,p))
         this.chart.newCursor=this.cursor;
    }
  }

  var p={x:0,y:0};

  this.mousedown=function(event) {
    this.thumbRect(r);
    this.chart.calcMouse(event,p);

    var r1=this.gripRect(r);
    this.resizeBegin=this.useRange && contains(r1,p);
    r1.x+=r.width;

    this.resizeEnd=this.useRange && (!this.resizeBegin) && contains(r1,p);
    this.dragging=(!this.resizeBegin) && (!this.resizeEnd) && contains(r,p);

    if ((!this.resizeBegin) && (!this.resizeEnd)) {
      if (this.dragging)
        this.delta=(this.horizontal ? (r.x+r.width*0.5)-p.x : (r.y+r.height*0.5)-p.y);
      else
      if (contains(b,p))
      {
        var tmp=this.horizontal ? r.width*0.5 : r.height*0.5;
        this.delta=-tmp;
        this.clickAt(tmp+ (this.horizontal ? p.x : p.y));
      }
    }

    return this.dragging || this.resizeBegin || this.resizeEnd;
  }

  this.clicked=function() {
    var d=this.dragging || this.resizeBegin || this.resizeEnd;
    this.resizeBegin=this.resizeEnd=this.dragging=false;
    this.delta=0;
    return d;
  }

  this.mouseout=function() {
    this.resizeBegin=this.resizeEnd=this.dragging=false;
  }
}

Tee.Slider.prototype=new Tee.Tool();

Tee.Scroller=function(canvas,target) {
  Tee.Chart.call(this,canvas);

  this.target=target;

  this.aspect.clip=false;
  this.panel.transparent=true;
  this.title.visible=false;

  var scroller=this.scroller=new Tee.Slider(this);
  scroller.useRange=true;
  scroller.thumbSize=100;

  var u=scroller.thumb;
  u.shadow.height=0;
  u.transparency=0.6;
  u.stroke.fill="black";
  u.shadow.visible=false;

  scroller.horizontal=true;
  var b=scroller.bounds;
  b.x=0;
  b.y=0;
  b.width=this.bounds.width;
  b.height=this.bounds.height;
  scroller.margin=0;

  scroller.lock=false;

  this.tools.add(scroller);

  var o=this;

  target.ondraw=function() { if (!scroller.lock) o.draw(); }
  target.onscroll=function() {
    var a=this.axes.bottom, li=this.series, mi=li.minXValue(), ma=li.maxXValue(),
       dif=a.maximum-a.minimum;

    if (a.minimum<mi) { a.minimum=mi; a.maximum=a.minimum+dif; }
    if (a.maximum>ma) { a.maximum=ma; a.minimum=a.maximum-dif; }
  }

  this.useRange=function(value) {
    scroller.useRange=value;
    this.draw();
  }

  this.invertThumb=function(value) {
    scroller.invertThumb=value;
    this.draw();
  }

  scroller.onChanging=function(s,v) {
    var r=(s.thumbSize*(s.max-s.min)/s.bounds.width)*0.5,
        li=target.series, mi=li.minXValue(), ma=li.maxXValue();

    if ((v-r)<mi) v=mi+r;
    else
    if ((v+r)>ma) v=ma-r;

    target.axes.bottom.setMinMax(v-r,v+r);
    this.lock=true;

    //requestAnimFrame(function() {target.draw();});

    target.draw();
    this.lock=false;

    if (o.onChanging)
      o.onChanging(o,v-r,v+r);

    return v;
  }

  this.setBounds=function(x,y,width,height) {
    this.bounds.x=x;
    this.bounds.y=y;
    this.bounds.width=width;
    this.bounds.height=height;

    b.x=x;
    b.y=y;
    b.width=width;
    b.height=height;
  }

  scroller.onDrawThumb=function(s) {

    var r=target.chartRect, ctx=target.ctx;
	var cRect =  new Tee.Rectangle(scroller.bounds.x,scroller.bounds.y,scroller.bounds.width,scroller.bounds.height);
    target.chartRect=cRect;
    target.ctx=scroller.chart.ctx;

    function saveAxis(axis,data) {
      var res={mi:axis.minimum, ma:axis.maximum, sp:axis.startPos, ep:axis.endPos}
      restoreAxis(axis,data);
      return res;
    }

    function restoreAxis(axis,old) {
      axis.minimum=old.mi;
      axis.maximum=old.ma;
      axis.startPos=old.sp;
      axis.endPos=old.ep;
      axis.scale=(old.ep-old.sp)/(old.ma-old.mi);
    }

    var b=scroller.bounds,
        c=target,
        li=c.series,
        h,v;

    s.min=li.minXValue();
    s.max=li.maxXValue();

    h=saveAxis(c.axes.bottom,{sp:b.x,ep:b.x+b.width,mi:s.min,ma:s.max});
    v=saveAxis(c.axes.left,{sp:b.y,ep:b.y+b.height,mi:li.minYValue(),ma:li.maxYValue()});

    var p=(h.mi+h.ma)*0.5,dif=(h.ma-h.mi),ra;

    //if (s.position!=p) {
      s.thumbSize=dif*s.bounds.width/(s.max-s.min);
      ra=dif*0.5;
      if (o.onChanging) o.onChanging(o,p-ra,p+ra);
      s.position=p;
    //}

    c.series.each(function(s) 
	{ 
	   if (s.visible && s.useAxes){
		   
		   
		   if ((s.pointer != undefined)) //disable points in Scroller. Bug sizing
		   {
			   var oldVisible = s.pointer.visible;
			   s.pointer.visible = false;
			   s.draw();
			   s.pointer.visible = oldVisible;
		   }
		   else
		     s.draw();
	   }
	});

    restoreAxis(c.axes.bottom,h);
    restoreAxis(c.axes.left,v);

    c.chartRect=r;
    c.ctx=ctx;
  }
}

Tee.Scroller.prototype=new Tee.Chart();

Tee.SliderControl=function(canvas) {
  var tmp=new Tee.Chart(canvas);
  tmp.panel.transparent=true;
  tmp.title.visible=false;

  var s=new Tee.Slider(tmp);

  s.bounds.x=s.thumbSize+1;
  s.bounds.width=tmp.canvas.width-2*s.thumbSize-2;
  s.bounds.y=(tmp.canvas.height-s.bounds.height)*0.5;

  tmp.tools.add(s);
  return s;
}

Tee.CheckBox=function(chart,text,checked) {
  Tee.Annotation.call(this,chart);

  this.transparent=true;
  this.text=text;
  this.checked=checked || true;
  this.margins.left=10;

  this.cursor="pointer";
  
  this.check=new Tee.Format(chart);
  this.check.fill="white";

  this.draw=function() {
    Tee.Annotation.prototype.draw.call(this);

    var c=this.chart.ctx, x=this.position.x+2;

    var h=this.bounds.height*0.6, y=this.position.y+(this.bounds.height-h)*0.4;

    this.check.rectangle(x,y,h,h);

    if (this.checked) {
      c.beginPath();
      c.moveTo(x+3,y+5);
      c.lineTo(x+4,y+8);
      c.lineTo(x+7,y+2);
      this.check.stroke.prepare();
      c.stroke();
    }
  }

  this.chart.canvas.addEventListener('touchstart', function(){
	  
  });
  this.onclick=function( /*a,x,y*/ ) {
	  this.checked=!this.checked;
    if (this.onchange) this.onchange(this);
    return true;
  }

}

Tee.CheckBox.prototype=new Tee.Annotation();
//*********** extras end ********************

//*********** animations start **************

if (typeof exports !== 'undefined') exports.Tee=Tee;

/**
 * @constructor
 * @augments Tee.Animation
 * @class Fades in/out chart elements.
 */
Tee.FadeAnimation=function(target) {
  Tee.Animation.call(this,target);

  this.kind="in"; // in, out

  var o=this, fa;

  this.fade={}

  this.setTransp=function(value) {

    if (o.kind=="out") value=1-value;

    if (fa.legend)
        o.chart.legend.format.transparency=value;

    if (fa.walls)
        o.chart.walls.transparency=value;

    if (fa.series)
        o.chart.series.each(function(s) { s.format.transparency=value; });

    if (fa.marks)
        o.chart.series.each(function(s) { s.marks.transparency=value; });

    if (fa.title)
        o.chart.title.format.transparency=value;

    if (fa.axes)
        o.chart.axes.transparency=value;

    if (fa.panel)
        o.chart.panel.format.transparency=value;
  }

  this.start=function() { fa=this.fade; this.setTransp(1); }
  this.stop=function() { this.setTransp(0); }
  this.doStep=function(f) { o.setTransp(1-f); }
}

Tee.FadeAnimation.prototype=new Tee.Animation();

/**
 * @constructor
 * @augments Tee.Animation
 * @class Animates series data
 * @property {Tee.Series} series Optional Tee.Series object to animate. When null, all series and axes are animated.
 * @property {String} [kind="axis"] Animation style. Can be: axis, left, top, right, bottom, x, y, each, all, zoomin, zoomout.
 */
Tee.SeriesAnimation=function(target) {
  Tee.Animation.call(this,target);

  if (target instanceof Tee.Series) {
    this.series=target;
    this.chart=target.chart;
  }
  else
    this.series=null;

  this.oldmin=0;
  this.oldmax=0;
  this.oldauto=true;

  var scaling=1, o=this;

  this.kind="axis"; // "left", "right", "top", "bottom", "axis", "x", "y", "zoomin", "zoomout", "each", "all"

  function changeAxis(o,a,amount) {
    a.automatic=false;
    var mid=(o.oldmin+o.oldmax)*0.5, range=(o.oldmax-o.oldmin)*0.5;
    a.maximum=mid+amount*range;
    a.minimum=mid-amount*range;
  }

 /**
  * @returns {Tee.Axis} Returns the mandatory axis of the animated series, or null
  * if no visible series exist.
  */
  this.getAxis=function() {
    var s=this.series || this.chart.series.firstVisible();
    return s ? s.mandatoryAxis : null;
  }

  this.getOtherAxis=function() {
    var s=this.series || this.chart.series.firstVisible();
    if (s) {
      if (s.yMandatory) {
        if (s.vertAxis === "both")
          return this.chart.axes.right;
      }
      else if (s.horizAxis === "both")
        return this.chart.axes.top;
    }
    else return null;
  }

  this.doStep=function(f) {

    var a=o.getAxis(), a2=o.getOtherAxis();
    if (a) {
      a.automatic=false;
    }
    if (a2) {
      a2.automatic=false;
    }

    if (o.kind=="axis") {
       changeAxis(o,a,1+(1-f)*100);
       if (a2)
         changeAxis(o,a2,1+(1-f)*100);
    }
    else
    o.chart.series.each(function(s) {
      if (o.series && (o.series!==s)) return;

      var v=s.data.values, old=s.data._old, t, len=v.length;

                    if (s instanceof Tee.ActivityGauge) {
                        s.maxDrawWidth = s.maxWidth * (f);
                    }
                    else
                    if (s instanceof Tee.Pie) {
        s.rotation=360*(1-f);
        scaling=f;
      }
      else
      if (o.kind=="each") {
       var stepf=trunc(len*f);

       for(t=0; t<stepf; t++) v[t]=old[t];

       if (stepf<len)
          v[stepf]=old[stepf]*((len*f)-stepf);
      }
      else
      if (o.kind=="all") {
       for(t=0; t<len; t++) v[t]=old[t]*f;
      }
      else
      if (o.kind!="axis") {
        scaling=f;
      }
    });
  }

  this.stop=function() {
            this.doStep(1);
    var a=o.getAxis(), a2=o.getOtherAxis();

    if (a) {
      a.maximum=o.oldmax;
      a.minimum=o.oldmin;
      a.automatic=o.oldauto;
    }
    if (a2) {
      a2.maximum=o.oldmax;
      a2.minimum=o.oldmin;
      a2.automatic=o.oldauto;
    }

    o.chart.series.each(function(s) {
      if (s.transform)
         s.transform=null;

      if ((o.kind=="each") || (o.kind=="all"))
      if (s.data._old)
      {
         s.data.values=s.data._old;
         s.data._old=null;
      }
    });
  }

  this.start=function() {

    var a=this.getAxis(), a2=this.getOtherAxis(), c=this.chart, ss=c.series.items,
        w=c.chartRect.width, h=c.chartRect.height, t, s,
        ww=c.bounds.width, hh=c.bounds.height;

    if (ss.length===0)
      return false;

    this.oldmin=a.minimum;
    this.oldmax=a.maximum;
    this.oldauto=a.automatic;

    for (t=0; t<ss.length; t++) {
      s=ss[t];

      if (this.series && (this.series!==s)) continue;

      if (s instanceof Tee.Pie)
        s.transform=function() { this.chart.ctx.scale(scaling, scaling); }
      else
      if ((this.kind=="each") || (this.kind=="all")) {
        var v=s.data.values, tt, len=v.length;
        s.data._old=v.slice(0);
        for(tt=0; tt<len; tt++) v[tt]=0;
        a.automatic=false;
        if (a2)
          a2.automatic=false;
      }
      else
      if (this.kind=="left")
        s.transform=function() { this.chart.ctx.translate(-w*(1-scaling),0); }
      else
      if (this.kind=="right")
        s.transform=function() { this.chart.ctx.translate(w*(1-scaling),0); }
      else
      if (this.kind=="x")
        s.transform=function() { this.chart.ctx.scale(scaling, 1); }
      else
      if (this.kind=="y")
        s.transform=function() { this.chart.ctx.scale(1, scaling); }
      else
      if (this.kind=="top")
         s.transform=function() { this.chart.ctx.translate(0,-h*(1-scaling)); }
      else
      if (this.kind=="bottom")
         s.transform=function() { this.chart.ctx.translate(0,h*(1-scaling)); }
      else
      if (this.kind=="zoomin")
         s.transform=function() {
           var ctx=this.chart.ctx;
           ctx.translate(ww*0.5,hh*0.5);
           ctx.scale(scaling,scaling);
           ctx.translate(-ww*0.5,-hh*0.5);
         }
      else
      if (this.kind=="zoomout")
         s.transform=function() {
           var ctx=this.chart.ctx;
           ctx.translate(ww*0.5,hh*0.5);
           ctx.scale(2-scaling,2-scaling);
           ctx.translate(-ww*0.5,-hh*0.5);
         }
    }

    if (this.kind=="axis") {
      changeAxis(this, a, 100);
      if (a2)
        changeAxis(this, a2, 100);
    }
  }
}

Tee.SeriesAnimation.prototype=new Tee.Animation();


/**
 * @constructor
 * @augments Tee.Animation
 * @class Animates Series marks items.
 */
Tee.MarksAnimation=function(target) {
  Tee.Animation.call(this,target);

  if (target && (target instanceof Tee.Series)) {
    this.series=target;
    this.chart=target.chart;
  }
  else
    this.series=null;

  this.current=-1;

  var m=this.series.marks, o=this, old;

  function marksText(series,index,result) {
    if (index<=o.current)
       return result;
    else
       return "";
  }

  this.start=function() {
     old=m.ongettext;
     m.ongettext=marksText;
  }

  this.stop=function() {
     m.ongettext=old;
     this.current=-1;
  }

  this.doStep=function(f) {
    o.current=trunc(o.series.data.values.length*f);
  }
}

Tee.MarksAnimation.prototype=new Tee.Animation();

//*********** animations end ****************
 
  
}.call(this));

/*TOUCH FUNCTIONS*/
/**
 * Double tap function reset the axes of the chart if canvas is double tapped.
 */
function doubleTap(chart) {
  var canvas = chart.canvas;
  var timeout;
  var lastTap = 0;
  canvas.addEventListener("touchend", function (e) {
    var currentTime = new Date().getTime();
    var tapLength = currentTime - lastTap;
    clearTimeout(timeout);
    if (tapLength < 600 && tapLength > 100) {
      chart.zoom.reset();
      chart.draw();
    } else {
      timeout = setTimeout(function () {
        clearTimeout(timeout);
      }, 600);
    }
    lastTap = currentTime;
    e.preventDefault();
  });
}

/**
 * Zoom the axes of the chart changing the min and the max value when two fingers are touching and moving in the canvas.
 */

function twoFingersZoom(chart, zoom) {
  var canvas = chart.canvas;
  var timer;
  var maxDistX = 0,
    minDistX = 0,
    maxDistY = 0,
    minDistY = 0;
  var newMinDistX = 0,
    newMaxDistX = 0,
    newMinDistY = 0,
    newMaxDistY = 0;
  var oldMinDistX = 0,
    oldMaxDistX = 0,
    oldMinDistY = 0,
    oldMaxDistY = 0;
  var touches = [];
  var touchedMoreThanOnceStart;
  var pMinX, pMinY, pMaxX, pMaxY;
  var iniMinX, iniMinY, iniMaxX, iniMaxY;
  touchedMoreThanOnceStart = false;
  timer = setInterval(touchZoom, 100);

  function touchZoom() {
    var len = touches.length;
    if (len > 1) {
      var tmp = 0;
      var touch1 = touches[0];
      var touch2 = touches[1];
      zoom.touching = true;
      if (!touchedMoreThanOnceStart) {
        iniMinX = touch1.pageX;
        iniMaxX = touch2.pageX;
        iniMinY = touch1.pageY;
        iniMaxY = touch2.pageY;

        if (iniMinX > iniMaxX) {
          tmp = iniMinX;
          iniMinX = iniMaxX;
          iniMaxX = tmp;
        }
        if (iniMinY > iniMaxY) {
          tmp = iniMinY;
          iniMinY = iniMaxY;
          iniMaxY = tmp;
        }

        touchedMoreThanOnceStart = true;
      } else {
        pMinX = touch1.pageX;
        pMaxX = touch2.pageX;
        pMinY = touch1.pageY;
        pMaxY = touch2.pageY;
        if (pMinX > pMaxX) {
          tmp = pMinX;
          pMinX = pMaxX;
          pMaxX = tmp;
        }
        if (pMinY > pMaxY) {
          tmp = pMinY;
          pMinY = pMaxY;
          pMaxY = tmp;
        }

        newMinDistX = pMinX - iniMinX;
        newMinDistY = pMinY - iniMinY;
        newMaxDistX = pMaxX - iniMaxX;
        newMaxDistY = pMaxY - iniMaxY;

        minDistX = oldMinDistX - newMinDistX;
        minDistY = oldMinDistY - newMinDistY;
        maxDistX = oldMaxDistX - newMaxDistX;
        maxDistY = oldMaxDistY - newMaxDistY;

        oldMinDistX = newMinDistX;
        oldMinDistY = newMinDistY;
        oldMaxDistX = newMaxDistX;
        oldMaxDistY = newMaxDistY;
      }
      if (zoom.direction == "both" || zoom.direction == "horizontal") {
        drawMinMaxBottom(
          chart.axes.bottom.startPos + minDistX,
          chart.axes.bottom.endPos + maxDistX
        );
      }
      if (zoom.direction == "both" || zoom.direction == "vertical") {
        drawMinMaxLeft(
          chart.axes.left.startPos + minDistY,
          chart.axes.left.endPos + maxDistY
        );
      }
    }
  }

  canvas.addEventListener("touchend", function () {
    if (touchedMoreThanOnceStart) {
      touchedMoreThanOnceStart = false;
    }
    clearInterval(timer);
  });
  canvas.addEventListener("touchmove", function (event) {
    event.preventDefault();
    touches = event.touches;
  });

  function drawMinMaxBottom(min, max) {
    chart.axes.top.calcMinMax(min, max);
    chart.axes.bottom.calcMinMax(min, max);
    chart.draw();
  }
  function drawMinMaxLeft(min, max) {
    chart.axes.right.calcMinMax(min, max);
    chart.axes.left.calcMinMax(min, max);
    chart.draw();
  }
}

/**
* @constructor
* @augments Tee.Series
* plots a Sliced Treemap
* @class plots a sliced Treemap.
*/
Tee.Sliced = function(o, o2) {  //1r algortime de treemap
  Tee.Series.call(this,o,o2);
  
  this.useAxes=false;
  this.colorEach = "yes";
  
  this.draw = function() {
    if (this.data && this.data.values && this.data.labels) {
      var ctx = this.chart.ctx;
	  
	  this.calcColorEach();
    
      var values = this.data.values;
      var labels = this.data.labels;
      var total = values.reduce((total, value) => total + value, 0);

      var sortedMap = values.map((value, index) => ({
        value: value,
        label: labels[index]
      })).sort((a, b) => b.value - a.value);

      var y = 0;
      var canvasHeight = this.chart.chartRect.height;
      var canvasWidth = this.chart.chartRect.width;

      for (var i = 0; i < sortedMap.length; i++) {
        var item = sortedMap[i];
        var percentage = item.value / total;
        var mida_corresponent = percentage * canvasHeight;

        ctx.beginPath();
        ctx.fillStyle = this.getFill(i, this.format);
        ctx.fillRect(0, y, canvasWidth, y + mida_corresponent);

        ctx.strokeStyle = "black";
        ctx.lineWidth = 2;
        ctx.strokeRect(0, y, canvasWidth, y + mida_corresponent);

        ctx.closePath();

        ctx.fillStyle = "white";
        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText(item.label, canvasWidth / 2, y + mida_corresponent / 2);

        ctx.stroke();

        y += mida_corresponent;

      }
    }
  };
};

Tee.Sliced.prototype = new Tee.Series();

/**
* @constructor
* @augments Tee.Series
* @plots a Treemap
* @class plots a squarified Treemap.
*/
Tee.Treemap = function(o, o2) {
    Tee.Series.call(this, o, o2);
	
	var f = this.format;
    f.stroke.fill = "black";
	f.font.style = "16px Arial";

	this.fillStyle = "white";
	this.lineWidth = 2;

    this.palette=new Tee.Palette([ "#4466a3", "#f39c35", "#f14c14", "#4e97a8", "#2b406b",
                "#1d7b63", "#b3080e", "#f2c05d", "#5db79e", "#707070",
                "#f3ea8d", "#b4b4b4"]);
				
    this.useAxes = false;
    
    this.drawNode = function(ctx,node, x, y, width, height, n){
      
        if (!node || node.length === 0) {
          return;
      }
        const children = node.children;

        
        let posy = y;
        let xoffset = x;

        ctx.beginPath();
        ctx.fillStyle = this.palette.get(n);
        ctx.fillRect(xoffset, posy, width - xoffset, height - posy);
        ctx.strokeStyle = f.stroke.fill;
        ctx.lineWidth = this.lineWidth;
        ctx.strokeRect(xoffset, posy, width - xoffset, height - posy);
        ctx.fillStyle = this.fillStyle;
        ctx.font = f.font.style;
        ctx.textAlign = this.format.textAlign;
        ctx.textBaseline = "middle";
		if (this.marks.visible){
			if(ctx.textAlign == "center"){
			  ctx.fillText(node.name, x + width / 2, posy + 10);
			}
		}
        else if (ctx.textAlign == "left"){
          ctx.fillText(node.name, x, posy + 10);
        }
        else if (ctx.textAlign == "right"){
          ctx.fillText(node.name,  width - x, posy + 10);
        }

        if(children == null) return;
        
        const childHeight = ((height - posy)- 10 * 2) / children.length;
        posy += 10;
        xoffset += 10;
        
        for (const child of children) {
            
            this.drawNode(ctx,child, xoffset, posy, width - xoffset, childHeight + posy, ++n);
            posy += childHeight;
        }
      }
    
    this.draw = function() {
        
            var ctx = this.chart.ctx;
          
            var root = o;

            var y = 0;
            var canvasHeight = this.chart.chartRect.height;
            var canvasWidth = this.chart.chartRect.width;

            this.drawNode(ctx,root, 0, y, canvasWidth, canvasHeight,0);
        
    };
};

Tee.Treemap.prototype = new Tee.Series();

/**
* @constructor
* @augments Tee.Series
* @class plots a LinearGauge.
* @property {Number} increment default 20 sets label increment
* @property {String} markSymbol defaulr '%'
* @property {string[]} gradientColors default ['red','green']
* @property {Number} animationIncrement default 1;
* @property {Number} startValue sets startvalue for animated bar
* @property {Number} finalValue sets startvalue for animated bar
*/
Tee.LinearGauge = function(o, o2){
  Tee.Series.call(this, o, o2);
  
  var f = this.format;
  f.stroke.fill = "black";
  f.stroke.size = 1;
  f.font.style = "16px Arial";
  this.lineFill = '#000';  
  this.txtHeight = f.textHeight("Wj");
  this.min = 0;
  this.max = 100;
  this.increment = 20;
  this.markSymbol = '%'
  this.gradientColors = ['red','green'];
  this.animationIncrement = 1;
  
  this.startValue = (o == undefined) ? 0 : o;
  this.finalValue = (o2 == undefined) ? 0 : o2;
  this.value = this.startValue;
  
  this.useAxes = false;
  
  this.drawAll = function(value, ctx, gaugeX, gaugeY,gradient,gaugeWidth,gaugeHeight){
	
	this.drawBar(value, ctx, gaugeX, gaugeY,gradient,gaugeWidth,gaugeHeight,true);
 
  }

  this.drawBar = function(value, ctx, gaugeX, gaugeY,gradient,gaugeWidth,gaugeHeight,first){
	
	this.value = value;

    for (let i = this.min; i <= this.max; i += this.increment) {
      const x = this.gaugeX + (this.gaugeWidth * i / 100);
      ctx.fillStyle = this.lineFill;
	  ctx.lineWidth = f.stroke.size;	  
	  ctx.moveTo(x, gaugeY - 5);
	  ctx.lineTo(x, gaugeY + this.gaugeHeight + 5); 
      //ctx.fillRect(x, gaugeY - 5, 1, this.gaugeHeight + 10);
	  ctx.stroke();
	  if (first) {
		  ctx.font = this.chart.axes.bottom.labels.format.font.style;
		  var ltext = `${i}` + this.markSymbol;
		  ctx.fillText(ltext, x - (f.textWidth(ltext)/2), gaugeY - this.txtHeight);
		  var ltext = "";
	  }
    }
	
	ctx.fillStyle = gradient;
    ctx.fillRect(this.gaugeX, gaugeY, this.gaugeWidth * (value / 100), this.gaugeHeight);

    //mark 
	if (this.marks.visible)
	{
		ctx.fillStyle = this.marks.format.font.fill;
		ctx.font= this.marks.format.font.style;
		ctx.fillText(this.value, this.gaugeWidth * (value / 100) + 12, gaugeY + (this.gaugeHeight/2) - (this.marks.format.textHeight("Wj")/2) );		
	}
	
	ctx.strokeStyle = f.stroke.fill;
    ctx.lineWidth = f.stroke.size;
    ctx.strokeRect(this.gaugeX, gaugeY, this.gaugeWidth, this.gaugeHeight);	
  }

  this.animateGauge = function(currentValue, targetValue,gradient,gaugeX,gaugeY,gaugeWidth,gaugeHeight,ctx,first){
	if (currentValue != targetValue) 
	{
      if (Math.abs(currentValue - targetValue) <= this.animationIncrement)
         currentValue = targetValue;
      else if (currentValue < targetValue)
        currentValue += this.animationIncrement;
	  else 
		currentValue -= this.animationIncrement;
      
	  if (first)
		   this.drawAll(currentValue, ctx, this.gaugeX, gaugeY,gradient,this.gaugeWidth,this.gaugeHeight);
	  else
	  {
		  ctx.clearRect(this.gaugeX, gaugeY, this.gaugeWidth, this.gaugeHeight);
		  this.drawBar(currentValue, ctx, this.gaugeX, gaugeY,gradient,this.gaugeWidth,this.gaugeHeight,false);
	  }
      requestAnimationFrame(() => this.animateGauge(currentValue, targetValue,gradient,gaugeX,gaugeY,gaugeWidth,gaugeHeight,ctx,false));
	}
	//else
	//  this.drawAll(currentValue, ctx, this.gaugeX, gaugeY,gradient,this.gaugeWidth,this.gaugeHeight);	
  }

  this.draw = function() {

  var value = this.startValue;
  var ctx = this.chart.ctx;
  var height = this.chart.chartRect.height;
  var width = this.chart.chartRect.width;
  this.gaugeWidth = width - 20;
  this.gaugeHeight = 30;
  this.gaugeX = 30;  //leave space for 1st label.
  var gaugeY = (height - this.gaugeHeight) / 2;

  const gradient = ctx.createLinearGradient(0, 0, this.gaugeWidth, 0);
  gradient.addColorStop(0, this.gradientColors[0]);
  gradient.addColorStop(1, this.gradientColors[1]);

    if (this.animation == true) {
      let currentValue = value;
      const targetValue = this.finalValue;
	  if (currentValue == targetValue)
		this.drawAll(o2, ctx, this.gaugeX, gaugeY, gradient,this.gaugeWidth,this.gaugeHeight);
	  else
        this.animateGauge(currentValue, targetValue,gradient,this.gaugeX,gaugeY,this.gaugeWidth,this.gaugeHeight,ctx,true);
    } else {
      this.drawAll(o2, ctx, this.gaugeX, gaugeY, gradient,this.gaugeWidth,this.gaugeHeight);
    }
  }
}

Tee.LinearGauge.prototype = new Tee.Series();