Source: teechart.js

/**
 * @preserve TeeChart(tm) for JavaScript(tm)
 * @fileOverview TeeChart for JavaScript(tm)
 * v1.8 April 2015
 * Copyright(c) 2012-2015 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 1.8
 */

/**
 * @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.
 */
function Point(x,y) {
  this.x=x;
  this.y=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.
 */
function Rectangle(x,y,width,height)
{
  this.set(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.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
       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 {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.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;
  }

  // (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 (getbounds) {
         var r=getbounds();
         i.tryDraw(r.x,r.y,r.width,r.height);
       }
       else
         i.tryDraw(x,y,width,height);

       c.restore();
     }
     else
     {
       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();
       }
     }

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

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

  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++) {
          xy(rows[t]);
          y+=h;
        }
    }
    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 || "";

  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.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=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=function() {
    if (!isEmpty(this.text)) {

      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);
  }
}

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

Tee.Annotation.prototype.draw=function() {
  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.
 */
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.
 */
Tee.ToolTip=function(chart) {
  Tee.Annotation.call(this,chart);

  this.visible=false;

  /**
   * @private
   */
  this.currentSeries=null;
  /**
   * @private
   */
  this.currentIndex=-1;
  /**
   * @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:#666; border-radius:4px 4px; color:#FFF; z-index:1000;";

  this.hide=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=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) {
          ser=s;
          break;
        }
      }
    }

    if (index==-1) {
      this.hide();

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

      this.currentIndex=index;
      this.currentSeries=ser;

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

        if (this.autoHide && (this.delay > 0)) {
           clearTimeout(this.timID);
           this.timID=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=function(series,index) {
    var isDom = this.render==="dom";
    this.visible= !isDom;

    this.text=series.markText(index);

    if (this.ongettext)
      this.text=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);
           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) s.draw();
  }

  this.mousemove=function(p) {
    for(var t=0, s; s=this.items[t++];)
      if (s.active) 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.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.
   */
  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.labelStyle="auto"; // auto,value,mark,text

    this.dateFormat="shortDate";

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

      this._text=null;

      if (st=="auto") {
        if ((s.data.labels.length>0) && s.associatedToAxis(axis) && (axis.horizontal==s.notmandatory.horizontal))
           this._text=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);

         s= data.labels[v];

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

         if (!s) {
           var li=chart.series.items, t, ser;
           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
      {
		  if (this.fixedDecimals)
			  s = this.formatValueString(value);
		  else if (this.valueFormat) {
		    s=value.toFixed(v==value ? 0 : this.decimals);
            s = (s*1).toLocaleString(); //B558
          }			
      else
        s=value.toFixed(v==value ? 0 : this.decimals);		  
      }

      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) {
            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 (this.ongetlabel)
                  s = this.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 appropiate 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;

    //var w=this.calc(this.minimum+this.increm)-this.startPos;
    //if (w<l) this.increm*=2;
  }

 /**
  * @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)
       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) {
           r.x = -this.rotatedWidth(l, r.width) * 0.5;
           r.y = -(this.rotatedWidth(l, r.height) * 0.5)+3;
       }
       else {
           r.x = -2;
           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;

    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 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() {

  if (!this.visible) return;

  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.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();

  // 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;

    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.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"
 */
function Legend(chart)
{
  this.chart=chart;

  this.transparent=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;

      if (this.style==="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);
      }
      else
      {
        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();
      }

      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"))
      {}
      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 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;
      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;
          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;
        f.drawText(r, text.join(" "));
      }

    }

    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) {
      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;
          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.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();
    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();

      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){
  return Math.max.apply({},removeEmptyArrayElements(a));
}
/**
 * @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){
  return Math.min.apply({},removeEmptyArrayElements(a));
}

/**
 * @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.yMandatory=true;
  this.horizAxis="bottom";
  this.vertAxis="left";

  this.sequential=true;

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

  f.fill="";
  f.stroke.fill="";
  this.visible=true;

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

  s.visible=true;
  s.color="red";
  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.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
       {
         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; }

 /**
  * @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();
        else
        if (trunc(d)==d)
          return d.toFixed(0);
        else
          return d.toFixed(this.decimals);
      }
      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"
  */
  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
      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"
  */
  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.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++) {
        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++) {
        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="http://www.w3.org/TR/html5/the-canvas-element.html">element</a>.
 * @property {HTMLCanvasElement} canvas The <a href="http://www.w3.org/TR/html5/the-canvas-element.html">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;


  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;

  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);

  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.footer=new Title(this, "red");
  this.footer.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;
    }
    else //earlier Moz.
    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);

    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.setCapture)
          target.setCapture();
    }

    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) {

      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.releaseCapture)
        target.releaseCapture();
  }


  /*
  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.onmousedown = c.ontouchstart = this.domousedown;
  c.onmouseup = c.ontouchstop = this.domouseup;
  c.onmousemove = 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) 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();
      }
    }
  }

  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);

    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 (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.footer.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.draw();

   if (this.ondraw) this.ondraw(this);
  }

  /**
   * 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} [offset=0] Defines the percent bar size to offset each bar.
 * @property {String} [barStyle="bar"] Which shape to draw ("bar", "ellipse", "line").
 */
Tee.CustomBar=function(o,o2) {

  Tee.Series.call(this,o,o2);

  this.sideMargins=100;
  this.useOrigin=true;
  this.origin=0;

  this.marks.visible=true;
  this.marks.location='end';

  this.hover.enabled=true;

  this.offset=0; // %
  this.barSize=70; // %
  this.barStyle="bar"; // "ellipse"

  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-1);

    if (this.stacked=="no") {
      barSize /= visibleBar.total;
	  offset.x=(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=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.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;

			f.z = 0; //B407 init
      for(var t=0; t<len; t++)
      if (!this.isNull(t)) {
        this.calcStackPos(t,p);
        this.calcBarBounds(p,bar,offset,originPos);
        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 (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();

    this.calcBarOffset(range===0? nomand.axisSize : nomand.calcSize(range));

    if (this.useOrigin)
       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);

        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));
    if (this.mandatoryAxis.inverted) inv=!inv;

    // Marks location

    if (m.location=="center") {
      this.calcBarBounds(p,bar,offset,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;

    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, 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.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.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.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);

    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,3)) || (pointInLine(p,p1a,p2,3)))
             return t;
        }
        else
          if (pointInLine(p,p1,p2,3))  // 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";

  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;

    //if (is3d)
    //  var wx=a._orthox*0.5, wy=a._orthoy*0.5;

    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;
      }

      if (c.spline)
        c.spline(smop);
      else
        Tee.drawSpline(c, smop, this.smooth, true);

    }
    else
    {
      var noNulls = this.treatNulls !== "skip";

      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 (is3d) {
          p.x+=wx;
          p.y-=wy;
        }
        */

        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;
      }
    }

    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 (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.
 */
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;

  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;

 /**
  * @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();

    endAngle+=Math.PI*2*( Math.abs(this.data.values[index]) / total);

    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,false);

      if (this.donut!==0) {
        calcPos(endAngle,p);
        c.lineTo(p.x,p.y);
        c.arc(center.x,center.y,donutRadius,endAngle,angle,true);
      }

      c.closePath();
    }

    if (index==this.over)
      hoverang=angle;

    angle=endAngle;
  }

  this.fill=function(i) {
    return this.getFillStyle(this.chart.chartRect,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.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;

      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;

    this.marks.format.z = 0.5;

    for(t=0; t<len; t+=this.marks.drawEvery) {

      index= sorted ? sorted[t] : t;

      if (!this.isNull(index)) {
        endAngle+=Math.PI*2*(Math.abs(v[index]) /total);
        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;

  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;
      
      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);
        }
      }
      else
      //  if (!this.isNull(t)) <-- pending
      {
        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) {
          c.lineTo(p.x,originPos);
          c.lineTo(start,originPos);
        }
        else
        {
          c.lineTo(originPos,p.y);
          c.lineTo(originPos,start);
        }
      }

      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.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();

/**
 * @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) {
  Tee.Pie.call(this,o,o2);
  this.donut=50;
}

Tee.Donut.prototype=new Tee.Pie();

/**
 * @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.colorEach="yes";

  this.data.start=this.data.values;
  this.data.x=[];
  this.data.end=[];

  this.height=70;
  this.margin=new Point(6,6);

  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.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;

    _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;
  }

  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.
 */
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.useOrigin=false;
  this.origin=0;

  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 = 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= 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,
    speed = 10,
    timer = 10,
    endalpha = 97,
    alpha = 0,
    tt,
    ttstyle,
    h,
    target,
    ie = ((typeof document!=='undefined') && document.all) ? true : false;

 return{
  show: function(v,w, dest, domStyle){
    if (!tt){
      tt = document.createElement('div');
      tt.setAttribute('id','teetip1');

      target=dest;

      tt.className='teetip';
	  
	  tt.setAttribute("style", domStyle);

      document.body.appendChild(tt);

      ttstyle=tt.style;
      ttstyle.opacity = 0;

      // IE only:
      if (ie)
         ttstyle.filter = 'alpha(opacity=0)';
    }

    document.onmousemove = this.pos;

    ttstyle.display = 'block';
    ttstyle.position = 'absolute';

    tt.innerHTML = v;
    ttstyle.width = w ? w + 'px' : 'auto';

    if (!w && ie)
        ttstyle.width = tt.offsetWidth;

    if (tt.offsetWidth > 300)
      ttstyle.width = 300 + 'px';

    h = parseInt(tt.offsetHeight,10) + top;
    if (tt.timer) clearInterval(tt.timer);
    tt.timer = setInterval(function() { Tee.DOMTip.fade(1) },timer);
  },

  pos: function(e){
      var d = document.documentElement,
          u = ie ? e.clientY + d.scrollTop : e.pageY,
          l = ie ? e.clientX + d.scrollLeft : e.pageX;

      if ((u-h)<0) u=h;
      if (l<0) l=0;
      
      if (target) {
        if (l>(target.clientLeft+target.clientWidth-tt.offsetWidth - 25))
            l = target.clientLeft+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;
        }
      }
      */
      
      ttstyle.top = (u - h) + 'px';
      ttstyle.left = (l + left) + 'px';
  },

  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);
      }
  }
 };
}();

}).call(this);