Start of myway timeliness graphing
[busui.git] / js / flot / jquery.flot.js
blob:a/js/flot/jquery.flot.js -> blob:b/js/flot/jquery.flot.js
  /*! Javascript plotting library for jQuery, v. 0.7.
  *
  * Released under the MIT license by IOLA, December 2007.
  *
  */
   
  // first an inline dependency, jquery.colorhelpers.js, we inline it here
  // for convenience
   
  /* Plugin for jQuery for working with colors.
  *
  * Version 1.1.
  *
  * Inspiration from jQuery color animation plugin by John Resig.
  *
  * Released under the MIT license by Ole Laursen, October 2009.
  *
  * Examples:
  *
  * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
  * var c = $.color.extract($("#mydiv"), 'background-color');
  * console.log(c.r, c.g, c.b, c.a);
  * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
  *
  * Note that .scale() and .add() return the same modified object
  * instead of making a new one.
  *
  * V. 1.1: Fix error handling so e.g. parsing an empty string does
  * produce a color rather than just crashing.
  */
  (function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
   
  // the actual Flot code
  (function($) {
  function Plot(placeholder, data_, options_, plugins) {
  // data is on the form:
  // [ series1, series2 ... ]
  // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
  // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
   
  var series = [],
  options = {
  // the color theme used for graphs
  colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
  legend: {
  show: true,
  noColumns: 1, // number of colums in legend table
  labelFormatter: null, // fn: string -> string
  labelBoxBorderColor: "#ccc", // border color for the little label boxes
  container: null, // container (as jQuery object) to put legend in, null means default on top of graph
  position: "ne", // position of default legend container within plot
  margin: 5, // distance from grid edge to default legend container within plot
  backgroundColor: null, // null means auto-detect
  backgroundOpacity: 0.85 // set to 0 to avoid background
  },
  xaxis: {
  show: null, // null = auto-detect, true = always, false = never
  position: "bottom", // or "top"
  mode: null, // null or "time"
  color: null, // base color, labels, ticks
  tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
  transform: null, // null or f: number -> number to transform axis
  inverseTransform: null, // if transform is set, this should be the inverse function
  min: null, // min. value to show, null means set automatically
  max: null, // max. value to show, null means set automatically
  autoscaleMargin: null, // margin in % to add if auto-setting min/max
  ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
  tickFormatter: null, // fn: number -> string
  labelWidth: null, // size of tick labels in pixels
  labelHeight: null,
  reserveSpace: null, // whether to reserve space even if axis isn't shown
  tickLength: null, // size in pixels of ticks, or "full" for whole line
  alignTicksWithAxis: null, // axis number or null for no sync
   
  // mode specific options
  tickDecimals: null, // no. of decimals, null means auto
  tickSize: null, // number or [number, "unit"]
  minTickSize: null, // number or [number, "unit"]
  monthNames: null, // list of names of months
  timeformat: null, // format string to use
  twelveHourClock: false // 12 or 24 time in time mode
  },
  yaxis: {
  autoscaleMargin: 0.02,
  position: "left" // or "right"
  },
  xaxes: [],
  yaxes: [],
  series: {
  points: {
  show: false,
  radius: 3,
  lineWidth: 2, // in pixels
  fill: true,
  fillColor: "#ffffff",
  symbol: "circle" // or callback
  },
  lines: {
  // we don't put in show: false so we can see
  // whether lines were actively disabled
  lineWidth: 2, // in pixels
  fill: false,
  fillColor: null,
  steps: false
  },
  bars: {
  show: false,
  lineWidth: 2, // in pixels
  barWidth: 1, // in units of the x axis
  fill: true,
  fillColor: null,
  align: "left", // or "center"
  horizontal: false
  },
  shadowSize: 3
  },
  grid: {
  show: true,
  aboveData: false,
  color: "#545454", // primary color used for outline and labels
  backgroundColor: null, // null for transparent, else color
  borderColor: null, // set if different from the grid color
  tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
  labelMargin: 5, // in pixels
  axisMargin: 8, // in pixels
  borderWidth: 2, // in pixels
  minBorderMargin: null, // in pixels, null means taken from points radius
  markings: null, // array of ranges or fn: axes -> array of ranges
  markingsColor: "#f4f4f4",
  markingsLineWidth: 2,
  // interactive stuff
  clickable: false,
  hoverable: false,
  autoHighlight: true, // highlight in case mouse is near
  mouseActiveRadius: 10 // how far the mouse can be away to activate an item
  },
  hooks: {}
  },
  canvas = null, // the canvas for the plot itself
  overlay = null, // canvas for interactive stuff on top of plot
  eventHolder = null, // jQuery object that events should be bound to
  ctx = null, octx = null,
  xaxes = [], yaxes = [],
  plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
  canvasWidth = 0, canvasHeight = 0,
  plotWidth = 0, plotHeight = 0,
  hooks = {
  processOptions: [],
  processRawData: [],
  processDatapoints: [],
  drawSeries: [],
  draw: [],
  bindEvents: [],
  drawOverlay: [],
  shutdown: []
  },
  plot = this;
   
  // public functions
  plot.setData = setData;
  plot.setupGrid = setupGrid;
  plot.draw = draw;
  plot.getPlaceholder = function() { return placeholder; };
  plot.getCanvas = function() { return canvas; };
  plot.getPlotOffset = function() { return plotOffset; };
  plot.width = function () { return plotWidth; };
  plot.height = function () { return plotHeight; };
  plot.offset = function () {
  var o = eventHolder.offset();
  o.left += plotOffset.left;
  o.top += plotOffset.top;
  return o;
  };
  plot.getData = function () { return series; };
  plot.getAxes = function () {
  var res = {}, i;
  $.each(xaxes.concat(yaxes), function (_, axis) {
  if (axis)
  res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
  });
  return res;
  };
  plot.getXAxes = function () { return xaxes; };
  plot.getYAxes = function () { return yaxes; };
  plot.c2p = canvasToAxisCoords;
  plot.p2c = axisToCanvasCoords;
  plot.getOptions = function () { return options; };
  plot.highlight = highlight;
  plot.unhighlight = unhighlight;
  plot.triggerRedrawOverlay = triggerRedrawOverlay;
  plot.pointOffset = function(point) {
  return {
  left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left),
  top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top)
  };
  };
  plot.shutdown = shutdown;
  plot.resize = function () {
  getCanvasDimensions();
  resizeCanvas(canvas);
  resizeCanvas(overlay);
  };
   
  // public attributes
  plot.hooks = hooks;
   
  // initialize
  initPlugins(plot);
  parseOptions(options_);
  setupCanvases();
  setData(data_);
  setupGrid();
  draw();
  bindEvents();
   
   
  function executeHooks(hook, args) {
  args = [plot].concat(args);
  for (var i = 0; i < hook.length; ++i)
  hook[i].apply(this, args);
  }
   
  function initPlugins() {
  for (var i = 0; i < plugins.length; ++i) {
  var p = plugins[i];
  p.init(plot);
  if (p.options)
  $.extend(true, options, p.options);
  }
  }
   
  function parseOptions(opts) {
  var i;
   
  $.extend(true, options, opts);
   
  if (options.xaxis.color == null)
  options.xaxis.color = options.grid.color;
  if (options.yaxis.color == null)
  options.yaxis.color = options.grid.color;
   
  if (options.xaxis.tickColor == null) // backwards-compatibility
  options.xaxis.tickColor = options.grid.tickColor;
  if (options.yaxis.tickColor == null) // backwards-compatibility
  options.yaxis.tickColor = options.grid.tickColor;
   
  if (options.grid.borderColor == null)
  options.grid.borderColor = options.grid.color;
  if (options.grid.tickColor == null)
  options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
   
  // fill in defaults in axes, copy at least always the
  // first as the rest of the code assumes it'll be there
  for (i = 0; i < Math.max(1, options.xaxes.length); ++i)
  options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
  for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
  options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
   
  // backwards compatibility, to be removed in future
  if (options.xaxis.noTicks && options.xaxis.ticks == null)
  options.xaxis.ticks = options.xaxis.noTicks;
  if (options.yaxis.noTicks && options.yaxis.ticks == null)
  options.yaxis.ticks = options.yaxis.noTicks;
  if (options.x2axis) {
  options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
  options.xaxes[1].position = "top";
  }
  if (options.y2axis) {
  options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
  options.yaxes[1].position = "right";
  }
  if (options.grid.coloredAreas)
  options.grid.markings = options.grid.coloredAreas;
  if (options.grid.coloredAreasColor)
  options.grid.markingsColor = options.grid.coloredAreasColor;
  if (options.lines)
  $.extend(true, options.series.lines, options.lines);
  if (options.points)
  $.extend(true, options.series.points, options.points);
  if (options.bars)
  $.extend(true, options.series.bars, options.bars);
  if (options.shadowSize != null)
  options.series.shadowSize = options.shadowSize;
   
  // save options on axes for future reference
  for (i = 0; i < options.xaxes.length; ++i)
  getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
  for (i = 0; i < options.yaxes.length; ++i)
  getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
   
  // add hooks from options
  for (var n in hooks)
  if (options.hooks[n] && options.hooks[n].length)
  hooks[n] = hooks[n].concat(options.hooks[n]);
   
  executeHooks(hooks.processOptions, [options]);
  }
   
  function setData(d) {
  series = parseData(d);
  fillInSeriesOptions();
  processData();
  }
   
  function parseData(d) {
  var res = [];
  for (var i = 0; i < d.length; ++i) {
  var s = $.extend(true, {}, options.series);
   
  if (d[i].data != null) {
  s.data = d[i].data; // move the data instead of deep-copy
  delete d[i].data;
   
  $.extend(true, s, d[i]);
   
  d[i].data = s.data;
  }
  else
  s.data = d[i];
  res.push(s);
  }
   
  return res;
  }
   
  function axisNumber(obj, coord) {
  var a = obj[coord + "axis"];
  if (typeof a == "object") // if we got a real axis, extract number
  a = a.n;
  if (typeof a != "number")
  a = 1; // default to first axis
  return a;
  }
   
  function allAxes() {
  // return flat array without annoying null entries
  return $.grep(xaxes.concat(yaxes), function (a) { return a; });
  }
   
  function canvasToAxisCoords(pos) {