/* graph.js - Paul Seamons - License BSD - Copyright 2010,2011 */
/* $Id: canvas_graph.js,v 1.33 2011-03-21 18:36:58 paul Exp $ */

var CanvasGraph = function (args) {
 var g = this;
 g.bar_width   = .6;
 g.background  = [0,'#bbb',.7,'#fff'];
 g.box_width   = 1;
 g.color       = '#777';
 g.canvas_color= '#fff';
 g.font        = '10px sans-serif';
 g.padding     = 10;
 g.tick_length = 4;
 g.legend      = 'topleft';
 for (var i in args) g[i] = args[i];
 for(var i=1;i<arguments.length;i++) if(arguments[i]) for(var j in arguments[i]) g[j]=arguments[i][j];

 var d = document;
 if (!g.canvas_id) {
  if (! d._graph_id) d._graph_id = 0;
  g.canvas_id = 'graph_'+(d._graph_id++);
 }
 if (! (g.canvas = d.getElementById(g.canvas_id))) {
  var w = args.width  ? args.width  : args.height ? parseInt(args.height * 5/3) : 500;
  var h = args.height ? args.height : (''+w).match(/^\d+$/) ? parseInt(w * 3/5) : 300;
  d.write('<canvas id="'+g.canvas_id+'" class="graph" width="'+w+'" height="'+h+'"></canvas>');
  g.canvas = d.getElementById(g.canvas_id);
  g.canvas.style.border = this.border || '1px solid #aaa'
 }

 var _attach = function (el, evname, func, capture) {
  if (el.addEventListener) return el.addEventListener(evname, func, capture ? true : false);
  if (el.attachEvent)      return el.attachEvent('on'+evname, func);
 }
 var _detach = function (el, evname, func, capture) {
  if (el.removeEventListener) return el.removeEventListener(evname, func, capture ? true : false);
  if (el.detachEvent)         return el.detachEvent('on'+evname, func);
 }
 _attach(window, 'load', args.load || function () { g.draw(1) }, true);

 // handle resizing
 var handle_resize = function (e) { g.draw(1) };
 var handle_up = function (e) {
  delete g.m_down;
  _detach(document, 'mouseup',   arguments.callee,   true);
  _detach(document, 'mousemove', handle_move, true);
 };
 var handle_move = function (e) {
  g.handleMove(e);
  if (g.m_down) {
   g.canvas.width  = g.m_down.w + e.clientX - g.m_down.x;
   g.canvas.height = g.m_down.h + e.clientY - g.m_down.y;
   _detach(window, 'resize', handle_resize, false);
   if (g.drawn) g.draw(1);
  }
  return true;
 };
 if (!args.no_interact) {
  _attach(g.canvas, 'mousemove', handle_move, true);
  _attach(g.canvas, 'mouseout',  function (e) { g.del_hi(); g.draw() },  true);
  _attach(g.canvas, 'mousedown', function (e) {
   if (!e) e = window.event;
   g.m_down = {x: e.clientX, y: e.clientY, w: g.canvas.clientWidth, h: g.canvas.clientHeight};
   _attach(document, 'mouseup',   handle_up, true);
   _attach(document, 'mousemove', handle_move, true);
  }, true);
 }
};

if (!CanvasGraph.prototype.draw) (function(){
var CP = CanvasGraph.prototype;
var is_un = function (o) { return typeof o == 'undefined' };

var get_xy = function (el) {
    var d=document;
 if (! el.offsetParent || el.tagName == 'BODY') return {x:-d.body.scrollLeft-d.documentElement.scrollLeft,y:-d.body.scrollTop-d.documentElement.scrollTop};
 var _xy = get_xy(el.offsetParent);
 return {x: el.offsetLeft + _xy.x, y: el.offsetTop + _xy.y};
};
var cmap = {
  blue:  '#0000FF', darkblue:  '#00008B',
  red:   '#FF0000', darkred:   '#8B0000',
  green: '#008000', darkgreen: '#006400',
  orange:'#FFA500', darkorange:'#FF8C00',
  yellow:'#FFFF00', gold:  '#FFD700',
  purple:'#800080', indigo:'#4B0082',
  black: '#000000', gray:  '#808080', white: '#FFFFFF'
};

CP.cmap = function () { return cmap };

CP.handleMove = function (e) {
 var g = this;
 var mmap = g.mousemap; if (!mmap) return;
 var xy = get_xy(g.canvas);
 var x = e.clientX - xy.x;
 var y = e.clientY - xy.y;
 var i,j;
 for (i=0; i < mmap.length; i++) {
  if (x < mmap[i][1] || x > mmap[i][2]) continue;
  var min, J, ys = mmap[i][3];
  for (var j=0; j < ys.length; j++) {
   if (is_un(ys[j])) continue;
   var dy = Math.abs(ys[j] - y);
   if (!is_un(min) && min <= dy) continue;
   min = dy;
   J   = j;
  }
  j = J;
  if (g.hi && (g.hi.i != i  || g.hi.j != j)) g.del_hi();
  break;
 }

 if (!g.m_down && !is_un(i) && !is_un(j)) {
  var d;
  if (!g.hi) { d=1; g.hi = {'i':i,'j':j,text:''+g.series[j].data[i]} }
  if (g.m_over && !g.m_over({series:j,'i':i,px:mmap[i][0]+xy.x,py:mmap[i][3][j]+xy.y,ex:e.clientX,ey:e.clientY,text:g.series[j].data[i],is_move:(d?0:1)})) return;
  if (d) g.draw();
 }
}

CP.del_hi = function () {
 if (this.m_out) this.m_out();
 delete this.hi;
}

CP.text_metrics = function (s, ctx) {
 var m=(''+ctx.font).match(/^(\d+)px/);
 if (is_un(s) || !s.length) return {h:0,w:0};
 return {h: m?parseInt(m[1]):10, w:(s.length&&ctx.measureText)?ctx.measureText(s).width:0};
};

CP.calc_scale = function (a) {
 var y_max = !is_un(a.y_max) ? a.y_max : a.d_max;
 var y_min = !is_un(a.y_min) ? a.y_min : a.d_min;
 if (! a.zoom) {
  if (y_min > 0) y_min = 0;
  if (y_max < 0) y_max = 0;
 }
 var max = Math.max(Math.abs(y_min),Math.abs(y_max));
 var mag = max ? parseInt(Math.log(max)/Math.LN10) : 0;
 var p = !is_un(a.prec) ? a.prec : max > 10 ? 0 : 1-mag;

 var inc  = parseFloat(Math.pow(10, mag-(max>1?0:1)).toFixed(p));
 var Imin = parseInt(y_min/inc); if (Math.abs(Imin*inc-y_min) > Math.pow(10,mag-3)) Imin--;
 var Imax = parseInt(y_max/inc); if (Math.abs(Imax*inc-y_max) > Math.pow(10,mag-3)) Imax++;

 var f = (Imax - Imin < 3) ? 5 : (Imax - Imin < 7) ? 2 : (Imax - Imin > 15) ? .5 : 1;
 Imax *= f; Imin *= f; inc /= f;
 if ((Imax-1)*inc > y_max) Imax--;
 if ((Imin+1)*inc < y_min) Imin++;
 if (f>1 && mag<1) p++;

 var pxr = a.h / (Imax-Imin);
 var N = [];
 var yzero = 0;
 for (var i = Imin; i <= Imax; i++) {
  N.push([(i*inc).toFixed(p),parseFloat(((i-Imin)*pxr).toFixed(2))]);
  if (i==0) yzero = N[N.length-1][1];
 }

 return {'I':(Imax-Imin), 'inc':inc, px_zero:yzero, pxr:pxr/inc, 'N':N};
};

CP.parseFill = function (bg, dim, bound, ctx) {
 if (Function.prototype.isPrototypeOf(bg)) bg = bg(dim, bound, ctx);
 if (!Array.prototype.isPrototypeOf(bg)) return bg;
 var i = 0;
 if (Array.prototype.isPrototypeOf(bg[i])) dim = bg[i++];
 else dim[0] = dim[2] = 0;
 var g = ctx.createLinearGradient.apply(ctx, dim);
 for (;i < bg.length;) g.addColorStop(bg[i++], bg[i++]);
 return g;
}

CP.draw = function (sized) {
 var g = this;
 if (g.drawing) return; g.drawing = 1;
 var cv = g.canvas;
 if (sized) {
  cv.width  = cv.clientWidth;
  cv.height = cv.clientHeight;
 }
 var c  = cv.getContext('2d');
 var W  = cv.width;
 var H  = cv.height;

 var p = g.padding;
 var yt = p;
 var yb = H - p - g.tick_length;
 var xl = p;
 var xr = W - p;
 c.font = g.font || '10px sans-serif';
 var R  = g.rotateX || 0;
 if (R) R = R * Math.PI / 180;
 var L  = g.legend || 'none';

 var mm = g.minmax();

 // sizing requirements for labels
 var xlh = 0;
 var last_l;
 for (var i=0; i < g.n_points; i++) {
  var t = g.labels[i];
  if (is_un(t)) t = '';
  if (typeof t != 'object') g.labels[i] = t = {str: ''+t};
  if (is_un(t.w)) {
   var m = g.text_metrics(t.str,c);
   t.w = m.w;
   t.h = m.h;
  }
  var h = R ? (Math.abs(Math.cos(R)*t.h) + Math.abs(Math.sin(R)*t.w)) : t.h;
  if (xlh < h) xlh = h;
  last_l = t;
 }
 if (R > 0 && L != 'right' && last_l) {
  var w = Math.abs(Math.sin(R)*last_l.h) + Math.abs(Math.cos(R)*last_l.w);
  if (w > (xr-xl)/g.n_points/2) xr -= w - (xr-xl)/g.n_points/2;
 }

 var mmap = g.mousemap = [];

 c.fillStyle = g.parseFill(g.canvas_color,[0,0,W,H],[0,0,W,H],c);
 c.fillRect(0,0,W,H);

 // title
 var m = g.text_metrics(g.title||'',c);
 if (m.w) {
  var s = g.title_scale || 1.6;
  c.save();
  c.fillStyle = g.title_color || g.text_color || g.color;
  c.translate((xr+xl-m.w*s)/2,yt);
  c.scale(s,s);
  c.textBaseline = 'top';
  c.fillText(g.title,0,0);
  c.restore();
  yt += m.h*s;
  if (!L.match(/^top/)) yt += p;
 }

 // draw the legend box
 var legend;
 if (L != 'none') {
  legend = {w:0,h:0,t:[],m:g.legend_margin,s:g.legend_scale,p:g.legend_padding};
  if (!legend.s) legend.s = 1.3;
  if (is_un(legend.m)) legend.m = 7;
  if (is_un(legend.p)) legend.p = 10;
  for (var i=0; i < g.series.length; i++) {
   m = g.text_metrics(g.series[i].name||'',c) || {w:0,h:0};
   legend.t[i] = {w:m.w*legend.s,h:m.h*legend.s};
  }
  var gr = legend.gr = legend.t[0].h + 6;
  for (var i=0; i < g.series.length; i++) {
   var t = legend.t[i];
   if (L == 'right' || L == 'left') {
    legend.tall = 1;
    if (t.w > legend.w) legend.w = t.w;
    if (i != 0) legend.h += legend.m;
    t.x = 0;
    t.y = legend.h + t.h/2;
    legend.h += t.h;
   } else {
    legend.h = gr;
    if (i != 0) legend.w += g.text_metrics('m',c).h;
    legend.w += gr;
    t.x = legend.w;
    t.y = legend.h/2;
    legend.w += t.w;
   }
  }
  legend.draw = function () {
   c.save();
   c.fillStyle = g.legend_color || g.text_color || g.color;
   c.textBaseline = 'middle';
   c.translate(legend.x+legend.gr, legend.y);
   for (var i=0; i < g.series.length; i++) {
    var t = legend.t[i];
    c.save();
    c.translate(t.x, t.y);
    c.scale(legend.s,legend.s);
    c.fillText(g.series[i].name,0,0);
    c.restore();
   }
   c.restore();
  };
  if (L == 'right') {
   xr -= legend.w+legend.gr+legend.p;
   legend.x = xr+legend.p;
  } else if (L == 'left') {
   legend.x = xl;
   xl += legend.w+legend.gr+legend.p;
  } else if (L.match(/^top/)) {
   legend.y = yt;
   legend.x = (L == 'top') ? (xl+ (xr - xl)/2 - legend.w/2 - legend.gr) : xr - legend.w-legend.gr;
   yt += legend.h+2;
   if (L != 'topleft') legend.draw();
  } else if (L.match(/^bottom/)) {
   legend.y = yb - legend.h + legend.p;
   legend.x = (L == 'bottom') ? (xl+ (xr - xl)/2 - legend.w/2 - legend.gr) : xr - legend.w-legend.gr;
   yb -= legend.h
   if (L != 'bottomleft') legend.draw();
  }
 }

 m = g.text_metrics(g.title_y||'',c);
 if (m.w) {
  var s = g.title_y_scale || 1.3;
  xl += m.h*s + p;
 }

 // x axis label
 var draw_xlab;
 m = g.text_metrics(g.title_x||'',c);
 if (m.w) {
  var s = g.title_x_scale || 1.3;
  var _yb = yb;
  var _w  = m.w*s;
  draw_xlab = function (xl) {
   c.save();
   c.fillStyle = g.title_x_color || g.text_color || g.color;
   c.translate((xr+xl-_w)/2,_yb);
   c.scale(s,s);
   c.textBaseline = 'bottom';
   c.fillText(g.title_x,0,0);
   c.restore();
  };
  yb -= (m.h*s + p);
 }
 yb -= xlh;
 if (legend && (L == 'right' || L == 'left')) {
  legend.y = yt + (yb - yt)/2 - legend.h/2;
  legend.draw();
 }

 // y axis label
 m = g.text_metrics(g.title_y||'',c);
 if (m.w) {
  var s = g.title_y_scale || 1.3;
  c.save();
  c.fillStyle = g.title_y_color || g.text_color || g.color;
  c.translate(xl-m.h*s,(yb+yt+m.w*s)/2);
  c.scale(s,s);
  c.rotate(-Math.PI/2);
  c.textBaseline = 'middle';
  c.fillText(g.title_y,0,0);
  c.restore();
 }

 // y axis labels
 var S = g.calc_scale({h:(yb-yt), d_max:mm.max, d_min:mm.min, y_max:g.y_max, y_min:g.y_min});
 var N = S.N;
 var yzero = yb-S.px_zero;
 var tmax = -1;
 for (var i=0; i < N.length; i++) {
  N[i][2] = g.text_metrics(N[i][0],c).w;
  if (tmax < N[i][2]) tmax = N[i][2];
 }
 if (tmax > 0) {
  c.save();
  c.fillStyle = g.axis_color_y || g.text_color || g.color;
  c.textBaseline = 'middle';
  for (var i=0; i < N.length; i++)
   if (N[i][2]) c.fillText(N[i][0], xl + (tmax - N[i][2]), yb-N[i][1]);
  xl += tmax + 4;
  c.restore();
 }
 xl += g.tick_length;
 if (draw_xlab) draw_xlab(xl);
 if (L == 'bottomleft' || L == 'topleft') {
  legend.x = xl - legend.gr;
  legend.draw();
 }

 // x axis labels
 var brdw = g.box_width;
 var barw = (xr - xl) / g.n_points;
 c.strokeStyle = g.axis_color_x || g.axis_color || g.box_color || g.color;
 c.save();
 c.beginPath();
 c.lineWidth = brdw;
 for (var j=0; j < g.n_points; j++) {
  var x = xl + (barw / 2) + j * barw;
  c.moveTo(x, yb+brdw/2);
  c.lineTo(x, yb+brdw/2+g.tick_length);
  mmap[j] = [x,x-barw/2,x+barw/2, []];
 }
 c.stroke();
 c.restore();
 c.save();
 c.beginPath();
 c.lineWidth = brdw;
 c.moveTo(xl-brdw/2,yb); c.lineTo(xr+brdw/2,yb);
 c.stroke();
 c.closePath();
 c.restore();
 c.fillStyle = g.axis_color_x || g.text_color || g.color;
 c.textBaseline = 'bottom';
 for (var j=0; j < g.n_points; j++) {
  var a = g.labels[j];
  var s = a.str; if (is_un(s) || !s.length) continue;
  var x = xl + (barw / 2) + j * barw;
  if (R > 0) { c.save(); c.translate(x-Math.sin(R)*a.h/2, yb+Math.cos(R)*a.h+brdw+g.tick_length); c.rotate(R); c.fillText(s, 0, 0); c.restore() }
  else if (R < 0) { c.save(); c.translate(x-Math.sin(R)*a.h/2, yb+Math.cos(R)*a.h+brdw+g.tick_length); c.rotate(R); c.fillText(s, -a.w, 0); c.restore() }
  else c.fillText(s, x - (a.w/2), yb+(a.h/2)+brdw+g.tick_length);
 }

 // y axis
 c.strokeStyle = g.axis_color_y || g.axis_color || g.box_color || g.color;
 c.save();
 c.beginPath();
 c.lineWidth = brdw * .5;
 for (var i=0; i < N.length; i++) {
  var y = yb-N[i][1]; if(y%1 >= .75){y+=.5}else if(y%1 < .25){y-=.5}
  c.moveTo(xl-brdw/2, y);
  c.lineTo(xl-brdw/2-g.tick_length, y);
 }
 c.stroke();
 c.closePath();
 c.beginPath();
 c.lineWidth = brdw;
 c.moveTo(xl,yt-brdw/2); c.lineTo(xl, yb+brdw/2);
 c.closePath();
 c.stroke();
 c.restore();

 // bounding box
 c.save();
 c.beginPath();
 c.lineWidth = brdw;
 c.moveTo(xl,yt); c.lineTo(xr,yt); c.lineTo(xr, yb);
 c.strokeStyle = g.box_color || g.color;
 c.stroke();
 c.restore();
 xl += brdw/2;
 xr -= brdw/2;
 yt += brdw/2;
 yb -= brdw/2;

 // background
 c.save();
 c.beginPath();
 c.moveTo(xl,yt); c.lineTo(xr,yt); c.lineTo(xr, yb); c.lineTo(xl, yb);
 c.fillStyle = g.parseFill(g.background,[xl,yt,xr,yb],[xl,yt,xr,yb],c);
 c.fill();
 c.closePath();
 c.restore();

 // lines in the back
 c.save();
 c.lineWidth = 1;
 for (var i=1; i < N.length - 1; i++) {
  var y = yb-N[i][1]; if(y%1 >= .75){y+=.5}else if(y%1 < .25){y-=.5}
  c.moveTo(xl,y); c.lineTo(xr,y);
 }
 c.strokeStyle = g.bar_color_y || g.bar_color || g.box_color || g.color;
 c.stroke(); c.restore();

 // now for the data
 for (var i=0; i < g.series.length; i++) {
  var s = g.series[i];
  var lgn; if (legend) lgn = legend.t[i];
  c.save();
  c.translate(-1,0);//todo - why needed?
  c.beginPath();
  var first = undefined;
  var last;
  var points = [];
  for (var j=0; j < g.n_points; j++) {
   var n = s.data[j];
   if (is_un(n)) continue;
   var x = last = xl + (barw / 2) + j * barw;
   var y = yzero - S.pxr*n;
   mmap[j][3][i] = y;
   if (s.bar) {
    var x1 = x - (barw * g.bar_width) / 2;
    var x2 = x + (barw * g.bar_width) / 2;
    c.moveTo(x1, yzero);
    c.lineTo(x1, y);
    c.lineTo(x2, y);
    c.lineTo(x2, yzero);
    c.lineTo(x1, yzero);
   } else if (is_un(first)) {
    first = x;
    c.moveTo(x, y);
   } else
    c.lineTo(x, y);
   if (s.point) points.push([x,y,n]);
  }
  c.fillStyle = g.parseFill(s.fillStyle || s.color,[xl,yt,xr,yb],[xl,yt,xr,yb],c);
  if (s.bar) {
   c.closePath();
   c.fill();
  } else if (s.fill) {
   c.lineTo(last, yzero);
   c.lineTo(first,yzero);
   c.closePath();
   c.fill();
  }
  c.lineWidth   = s.linewidth || 1.5;
  c.strokeStyle = s.color;
  c.stroke();
  if (points.length) for (var k=0; k < points.length; k++) {
   c.beginPath();
   c.arc(points[k][0], points[k][1], s.point/2, 0, Math.PI*2, 0);
   c.fillStyle = g.parseFill(s.fillStyle || s.color,[x1,y,x2,yzero],[xl,yt,xr,yb],c);
   c.fill();
   c.stroke();
   c.closePath();
  }
  // show in the legend
  if (lgn) {
   c.save();
   c.beginPath();
   c.translate(legend.x+lgn.x, legend.y+lgn.y-(s.fill||s.bar?(legend.gr-6)/4:0));
   if (s.point) c.arc(legend.gr/2, 0, s.point/2, 0, Math.PI*2, 0);
   c.fillStyle = g.parseFill(s.fillStyle || s.color,[0,-legend.gr/2,legend.gr,legend.gr/2],[-legend.gr/2,legend.gr,legend.gr/2],c);
   c.fill();
   c.stroke();
   c.closePath();
   if (s.fill||s.bar) {
    c.beginPath();
    c.moveTo(3,0);
    c.lineTo(legend.gr-3,0);
    c.lineTo(legend.gr-3,(legend.gr-6)/2);
    c.lineTo(3,(legend.gr-6)/2);
    c.lineTo(3,0);
    c.fill();
    c.stroke();
    c.closePath();
   } else {
    c.beginPath();
    c.moveTo(3,0);
    c.lineTo(legend.gr-3,0);
    c.stroke();
    c.closePath();
   }
   c.restore();
  }
  c.restore();
 }

 if (g.hi) {
  var i = g.hi.i;
  var j = g.hi.j;
  var t = g.hi.text;
  var x = g.mousemap[i][0];
  var y = g.mousemap[i][3][j];
  var m = g.text_metrics(t,c);
  var w = m.w; if (w < 20) w = 20;
  var r = 5;
  c.save();
  c.beginPath();
  if (!g.series[j].point) c.moveTo(x,y);
  else c.arc(x,y, g.series[j].point/2+3, -Math.PI*.40, -Math.PI*.60, 0);
  c.lineTo(x-5, y-15);
  var X = x-w/2, Y = y-15;
  c.lineTo(X,Y); X -= r;
  c.bezierCurveTo(X+r/2,Y, X,Y-r/2, X,Y-r); Y-=r;
  c.lineTo(X,Y-m.h); Y-=m.h+r;;
  c.bezierCurveTo(X,Y+r/2, X+r/2,Y, X+r,Y); X=x+w/2;
  c.lineTo(X,Y); X += r;
  c.bezierCurveTo(X-r/2,Y, X,Y+r/2, X,Y+r); Y+=r+m.h;
  c.lineTo(X,Y); Y += r;
  c.bezierCurveTo(X,Y-r/2, X-r/2,Y, X-r,Y);
  c.lineTo(x+5,y-15);
  c.closePath();
  var grad = c.createLinearGradient(0,y-15-m.h-2*r,0,y-8);
  grad.addColorStop(.6,'rgba(190,190,190,.8)');
  grad.addColorStop(1,'rgba(190,190,190,.3)');
  c.fillStyle = grad;
  c.fill();
  c.strokeStyle = 'black';
  c.stroke();
  c.restore();
  c.save();
  c.fillStyle = 'black';
  c.textBaseline = 'middle';
  c.fillText(t,x-m.w/2, y-15-m.h/2-r);
  c.restore();
 }
 g.drawn = 1;
 delete g.drawing;
};

// calculate our bounds
CP.minmax = function () {
 var g = this;
 if (is_un(g.max)) {
  if (g.checkData()) return;
  delete g.min;
  for (var i=0; i < g.series.length; i++) {
   for (var j=0; j < g.n_points; j++) {
    var n = g.series[i].data[j];
    if (is_un(n)) continue;
    n = parseFloat(n);
    if (is_un(g.min) || g.min > n) g.min = n;
    if (is_un(g.max) || g.max < n) g.max = n;
   }
  }
 }
 return {'min': g.min||0, 'max': g.max||0};
};

// make sure all data is in the correct layout
CP.checkData = function () {
 var g = this;
 if (! g.labels) g.labels = [];
 if (! g.series) g.series = [];
 var S = g.series;
 if (! S.length) {
  if (! g.data) { alert("Missing data or series"); return }
  S[0] = {'data': g.data};
 }
 if (! g.n_points) {
  g.n_points = g.labels.length;
  for (var i=0; i < S.length; i++) if (g.n_points < S[i].data.length) g.n_points = S[i].data.length;
 }
 if (g.labels.length < g.n_points) g.labels.length = g.n_points;
 var c = g.colors    || ['darkblue', 'darkgreen', 'indigo', 'darkred', 'darkorange', 'gold', 'gray', 'black', 'blue', 'purple', 'green', 'red'];
 var l = g.linetypes || ['dashed', 'solid'];
 for (var i=0; i < S.length; i++) {
  var s = S[i];
  if (is_un(s.name)) s.name = 'Series '+(i+1);
  if (g.n_points < s.data.length) g.n_points = s.data.length;
  if (! s.color) s.color = c[i - parseInt(i / c.length) * c.length];
  s.color = cmap[s.color] || s.color;
  var m;
  if (! s.fillStyle) s.fillStyle
   = (m = s.color.match(/^rgba/)) ? s.color
   : (m = s.color.match(/^\#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i)) ? 'rgba('+parseInt(m[1],16)+','+parseInt(m[2],16)+','+parseInt(m[3],16)+','+(s.alpha||.4)+')'
   : (m = s.color.match(/^rgb\((.+)\)$/)) ? 'rgba('+m[1]+','+(s.alpha||.4)+')'
   : '';
 }
};

CP.add = function () {
 var g = this;
 g.checkData();
 g.labels.push(arguments[0]);
 var n = g.labels.length - g.n_points; if (n < 0) n = 0;
 for (var j=0; j<n; j++) g.labels.shift();
 for (var i=0; i<g.series.length; i++) {
  g.series[i].data.push(arguments[i+1]);
  for (var j=0; j<n; j++) g.series[i].data.shift();
 }
 delete g.max;
 delete g.min;
 if (g.drawn) g.draw();
};

})();

