jquery.flot.js
上传用户:stephen_wu
上传日期:2008-07-05
资源大小:1757k
文件大小:52k
源码类别:

网络

开发平台:

Unix_Linux

  1. /* Javascript plotting library for jQuery, v. 0.3.
  2.  *
  3.  * Released under the MIT license by iola, December 2007.
  4.  *
  5.  */
  6. (function($) {
  7.     function Plot(target_, data_, options_) {
  8.         // data is on the form:
  9.         //   [ series1 series2 ... ]
  10.         // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
  11.         // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label" }
  12.         
  13.         var series = [];
  14.         var options = {
  15.             // the color theme used for graphs
  16.             colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
  17.             legend: {
  18.                 show: true,
  19.                 noColumns: 1, // number of colums in legend table
  20.                 labelFormatter: null, // fn: string -> string
  21.                 labelBoxBorderColor: "#ccc", // border color for the little label boxes
  22.                 container: null, // container (as jQuery object) to put legend in, null means default on top of graph
  23.                 position: "ne", // position of default legend container within plot
  24.                 margin: 5, // distance from grid edge to default legend container within plot
  25.                 backgroundColor: null, // null means auto-detect
  26.                 backgroundOpacity: 0.85 // set to 0 to avoid background
  27.             },
  28.             xaxis: {
  29.                 ticks: null, // either [1, 3] or [[1, "a"], 3]
  30.                 noTicks: 5, // approximate number of ticks for auto-ticks
  31.                 tickFormatter: defaultTickFormatter, // fn: number -> string
  32.                 tickDecimals: null, // no. of decimals, null means auto
  33.                 min: null, // min. value to show, null means set automatically
  34.                 max: null, // max. value to show, null means set automatically
  35.                 autoscaleMargin: 0 // margin in % to add if auto-setting min/max
  36.             },
  37.             yaxis: {
  38.                 noTicks: 5,
  39.                 ticks: null,
  40.                 tickFormatter: defaultTickFormatter,
  41.                 min: null,
  42.                 max: null,
  43.                 autoscaleMargin: 0.02
  44.             },
  45.             points: {
  46.                 show: false,
  47.                 radius: 3,
  48.                 lineWidth: 2, // in pixels
  49.                 fill: true,
  50.                 fillColor: "#ffffff"
  51.             },
  52.             lines: {
  53.                 show: false,
  54.                 lineWidth: 2, // in pixels
  55.                 fill: false,
  56.                 fillColor: null
  57.             },
  58.             bars: {
  59.                 show: false,
  60.                 lineWidth: 2, // in pixels
  61.                 barWidth: 1, // in units of the x axis
  62.                 fill: true,
  63.                 fillColor: null
  64.             },
  65.             grid: {
  66.                 color: "#545454", // primary color used for outline and labels
  67.                 backgroundColor: null, // null for transparent, else color
  68.                 tickColor: "#dddddd", // color used for the ticks
  69.                 labelMargin: 3, // in pixels
  70.                 clickable: null
  71.             },
  72.             selection: {
  73.                 mode: null, // one of null, "x", "y" or "xy"
  74.                 color: "#e8cfac"
  75.             },
  76.             shadowSize: 4
  77.         };
  78.         var canvas = null, overlay = null;
  79.         var ctx = null, octx = null;
  80.         var target = target_;
  81.         var xaxis = {};
  82.         var yaxis = {};
  83.         
  84.         var plotOffset = { left: 0, right: 0, top: 0, bottom: 0};
  85.         var labelMaxWidth = 0;
  86.         var labelMaxHeight = 0;
  87.         var canvasWidth = 0;
  88.         var canvasHeight = 0;
  89.         var plotWidth = 0;
  90.         var plotHeight = 0;
  91.         var hozScale = 0;
  92.         var vertScale = 0;
  93.         
  94.         // initialize
  95.         series = parseData(data_);
  96.         parseOptions(options_);
  97.         fillInSeriesOptions();
  98.         constructCanvas();
  99.         bindEvents();
  100.         findDataRanges();
  101.         calculateRange(xaxis, options.xaxis);
  102.         extendXRangeIfNeededByBar();
  103.         calculateRange(yaxis, options.yaxis);
  104.         calculateTicks(xaxis, options.xaxis);
  105.         calculateTicks(yaxis, options.yaxis);
  106.         calculateSpacing();
  107.         draw();
  108.         insertLegend();
  109.         this.getCanvas = function() { return canvas; };
  110.         this.getPlotOffset = function() { return plotOffset; };
  111.         this.clearSelection = clearSelection;
  112.         this.setSelection = setSelection;
  113.         
  114.         function parseData(d) {
  115.             var res = [];
  116.             for (var i = 0; i < d.length; ++i) {
  117.                 var s;
  118.                 if (d[i].data) {
  119.                     s = {};
  120.                     for (var v in d[i])
  121.                         s[v] = d[i][v];
  122.                 }
  123.                 else {
  124.                     s = { data: d[i] };
  125.                 }
  126.                 res.push(s);
  127.             }
  128.             return res;
  129.         }
  130.         
  131.         function parseOptions(o) {
  132.             $.extend(true, options, o);
  133.         }
  134.         function constructCanvas() {
  135.             canvasWidth = target.width();
  136.             canvasHeight = target.height();
  137.             target.html(""); // clear target
  138.             target.css("position", "relative"); // for positioning labels and overlay
  139.             if (canvasWidth <= 0 || canvasHeight <= 0)
  140.                 throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
  141.             // the canvas
  142.             canvas = jQuery('<canvas width="' + canvasWidth + '" height="' + canvasHeight + '"></canvas>').appendTo(target).get(0);
  143.     if (jQuery.browser.msie) // excanvas hack
  144. canvas = window.G_vmlCanvasManager.initElement(canvas);
  145.             ctx = canvas.getContext("2d");
  146.             // overlay canvas for interactive features
  147.             overlay = jQuery('<canvas style="position:absolute;left:0px;top:0px;" width="' + canvasWidth + '" height="' + canvasHeight + '"></canvas>').appendTo(target).get(0);
  148.     if (jQuery.browser.msie) // excanvas hack
  149. overlay = window.G_vmlCanvasManager.initElement(overlay);
  150.             octx = overlay.getContext("2d");
  151.         }
  152.         function bindEvents() {
  153.             if (options.selection.mode != null) {
  154.                 $(overlay).mousedown(onMouseDown);
  155.                 // FIXME: temp. work-around until jQuery bug 1871 is fixed
  156.                 target.get(0).onmousemove = onMouseMove;
  157.             }
  158.             if (options.grid.clickable)
  159.                 $(overlay).click(onClick);
  160.         }
  161.         function findDataRanges() {
  162.             yaxis.datamin = xaxis.datamin = 0;
  163.             xaxis.datamax = yaxis.datamax = 1;
  164.             if (series.length == 0)
  165.                 return;
  166.             // get datamin, datamax start values
  167.             var i, found = false;
  168.             for (i = 0; i < series.length; ++i) {
  169.                 if (series[i].data.length > 0) {
  170.                     xaxis.datamin = xaxis.datamax = series[i].data[0][0];
  171.                     yaxis.datamin = yaxis.datamax = series[i].data[0][1];
  172.                     found = true;
  173.                     break;
  174.                 }
  175.             }
  176.             if (!found)
  177.                 return;
  178.             // then find real datamin, datamax
  179.             for (i = 0; i < series.length; ++i) {
  180.                 var data = series[i].data;
  181.                 for (var j = 0; j < data.length; ++j) {
  182.                     var x = data[j][0];
  183.                     var y = data[j][1];
  184.                     if (x < xaxis.datamin)
  185.                         xaxis.datamin = x;
  186.                     else if (x > xaxis.datamax)
  187.                         xaxis.datamax = x;
  188.                     if (y < yaxis.datamin)
  189.                         yaxis.datamin = y;
  190.                     else if (y > yaxis.datamax)
  191.                         yaxis.datamax = y;
  192.                 }
  193.             }
  194.         }
  195.         function getTickSize(noTicks, min, max, decimals) {
  196.             var delta = (max - min) / noTicks;
  197.             var magn = getMagnitude(delta);
  198.             var norm = delta / magn; // norm is between 1.0 and 10.0
  199.             var tickSize = 1;
  200.             if (norm < 1.5)
  201.                 tickSize = 1;
  202.             else if (norm < 2.25)
  203.                 tickSize = 2;
  204.             else if (norm < 3)
  205.                 tickSize = 2.5;
  206.             else if (norm < 7.5)
  207.                 tickSize = 5;
  208.             else
  209.                 tickSize = 10;
  210.             if (tickSize == 2.5 && decimals == 0)
  211.                 tickSize = 2;
  212.             
  213.             tickSize *= magn;
  214.             return tickSize;
  215.         }
  216.         
  217.         function calculateRange(axis, axisOptions) {
  218.             var min = axisOptions.min != null ? axisOptions.min : axis.datamin;
  219.             var max = axisOptions.max != null ? axisOptions.max : axis.datamax;
  220.             // check degenerate case
  221.             if (max - min == 0.0) {
  222.                 var widen;
  223.                 if (max == 0.0)
  224.                     widen = 1.0;
  225.                 else
  226.                     widen = 0.01;
  227.                 min -= widen;
  228.                 max += widen;
  229.             }
  230.             
  231.             axis.tickSize = getTickSize(axisOptions.noTicks, min, max, axisOptions.tickDecimals);
  232.                 
  233.             // consider autoscaling
  234.             var margin;
  235.             if (axisOptions.min == null) {
  236.                 // first add in a little margin
  237.                 margin = axisOptions.autoscaleMargin;
  238.                 if (margin != 0) {
  239.                     min -= axis.tickSize * margin;
  240.                     // make sure we don't go below zero if all
  241.                     // values are positive
  242.                     if (min < 0 && axis.datamin >= 0)
  243.                         min = 0;
  244.                     
  245.                     min = axis.tickSize * Math.floor(min / axis.tickSize);
  246.                 }
  247.             }
  248.             if (axisOptions.max == null) {
  249.                 margin = axisOptions.autoscaleMargin;
  250.                 if (margin != 0) {
  251.                     max += axis.tickSize * margin;
  252.                     if (max > 0 && axis.datamax <= 0)
  253.                         max = 0;
  254.                     
  255.                     max = axis.tickSize * Math.ceil(max / axis.tickSize);
  256.                 }
  257.             }
  258.             
  259.             axis.min = min;
  260.             axis.max = max;
  261.         }
  262.         function extendXRangeIfNeededByBar() {
  263.             if (options.xaxis.max == null) {
  264.                 // great, we're autoscaling, check if we might need a bump
  265.                 var newmax = xaxis.max;
  266.                 for (var i = 0; i < series.length; ++i)
  267.                     if (series[i].bars.show && series[i].bars.barWidth + xaxis.datamax > newmax)
  268.                         newmax = xaxis.max + series[i].bars.barWidth;
  269.                 xaxis.max = newmax;
  270.             }
  271.         }
  272.         function defaultTickFormatter(val) {
  273.             return "" + val;
  274.         }
  275.         function calculateTicks(axis, axisOptions) {
  276.             var i;
  277.             axis.ticks = [];
  278.             if (axisOptions.ticks) {
  279.                 var ticks = axisOptions.ticks;
  280.                 if ($.isFunction(ticks))
  281.                     // generate the ticks
  282.                     ticks = ticks({ min: axis.min, max: axis.max });
  283.                 
  284.                 // clean up the user-supplied ticks, copy them over
  285.                 for (i = 0; i < ticks.length; ++i) {
  286.                     var v, label;
  287.                     var t = ticks[i];
  288.                     if (typeof(t) == "object") {
  289.                         v = t[0];
  290.                         if (t.length > 1)
  291.                             label = t[1];
  292.                         else
  293.                             label = axisOptions.tickFormatter(v);
  294.                     }
  295.                     else {
  296.                         v = t;
  297.                         label = axisOptions.tickFormatter(v);
  298.                     }
  299.                     axis.ticks[i] = { v: v, label: label };
  300.                 }
  301.             }
  302.             else {
  303.                 // round to nearest multiple of tick size
  304.                 var start = axis.tickSize * Math.ceil(axis.min / axis.tickSize);
  305.                 // then spew out all possible ticks
  306.                 for (i = 0; start + i * axis.tickSize <= axis.max; ++i) {
  307.                     v = start + i * axis.tickSize;
  308.                     
  309.                     // round (this is always needed to fix numerical instability)
  310.                     var decimals = axisOptions.tickDecimals;
  311.                     if (decimals == null)
  312.                         decimals = 1 - Math.floor(Math.log(axis.tickSize) / Math.LN10);
  313.                     if (decimals < 0)
  314.                         decimals = 0;
  315.                     
  316.                     v = v.toFixed(decimals);
  317.                     axis.ticks.push({ v: v, label: axisOptions.tickFormatter(v) });
  318.                 }
  319.             }
  320.         }
  321.         
  322.         function calculateSpacing() {
  323.             // calculate spacing for labels, using the heuristic
  324.             // that the longest string is probably the one that takes
  325.             // up the most space
  326.             var i, max_label = "";
  327.             for (i = 0; i < yaxis.ticks.length; ++i) {
  328.                 var l = yaxis.ticks[i].label.length;
  329.                 if (l > max_label.length)
  330.                     max_label = yaxis.ticks[i].label;
  331.             }
  332.             // measure it
  333.             var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller" class="gridLabel">' + max_label + '</div>').appendTo(target);
  334.             labelMaxWidth = dummyDiv.width();
  335.             labelMaxHeight = dummyDiv.height();
  336.             dummyDiv.remove();
  337.             var maxOutset = 2; // grid outline line width
  338.             if (options.points.show)
  339.                 maxOutset = Math.max(maxOutset, options.points.radius + options.points.lineWidth/2);
  340.             for (i = 0; i < series.length; ++i) {
  341.                 if (series[i].points.show)
  342.                     maxOutset = Math.max(maxOutset, series[i].points.radius + series[i].points.lineWidth/2);
  343.             }
  344.             plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset;
  345.             
  346.             plotOffset.left += labelMaxWidth + options.grid.labelMargin;
  347.             plotOffset.bottom += labelMaxHeight + options.grid.labelMargin;
  348.             
  349.             plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
  350.             plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
  351.             hozScale = plotWidth / (xaxis.max - xaxis.min);
  352.             vertScale = plotHeight / (yaxis.max - yaxis.min);
  353.         }
  354.         
  355.         function draw() {
  356.             drawGrid();
  357.             drawLabels();
  358.             for (var i = 0; i < series.length; i++) {
  359.                 drawSeries(series[i]);
  360.             }
  361.         }
  362.         function tHoz(x) {
  363.             return (x - xaxis.min) * hozScale;
  364.         }
  365.         function tVert(y) {
  366.             return plotHeight - (y - yaxis.min) * vertScale;
  367.         }
  368.         function drawGrid() {
  369.             ctx.save();
  370.             ctx.translate(plotOffset.left, plotOffset.top);
  371.             // draw background, if any
  372.             if (options.grid.backgroundColor != null) {
  373.                 ctx.fillStyle = options.grid.backgroundColor;
  374.                 ctx.fillRect(0, 0, plotWidth, plotHeight);
  375.             }
  376.             
  377.             // draw the inner grid
  378.             ctx.lineWidth = 1;
  379.             ctx.strokeStyle = options.grid.tickColor;
  380.             ctx.beginPath();
  381.             var i, v;
  382.             for (i = 0; i < xaxis.ticks.length; ++i) {
  383.                 v = xaxis.ticks[i].v;
  384.                 if (v == xaxis.min || v == xaxis.max)
  385.                     continue;   // skip those lying on the axes
  386.                 ctx.moveTo(Math.floor(tHoz(v)) + ctx.lineWidth/2, 0);
  387.                 ctx.lineTo(Math.floor(tHoz(v)) + ctx.lineWidth/2, plotHeight);
  388.             }
  389.             for (i = 0; i < yaxis.ticks.length; ++i) {
  390.                 v = yaxis.ticks[i].v;
  391.                 if (v == yaxis.min || v == yaxis.max)
  392.                     continue;
  393.                 ctx.moveTo(0, Math.floor(tVert(v)) + ctx.lineWidth/2);
  394.                 ctx.lineTo(plotWidth, Math.floor(tVert(v)) + ctx.lineWidth/2);
  395.             }
  396.             ctx.stroke();
  397.             
  398.             // draw outline
  399.             ctx.lineWidth = 2;
  400.             ctx.strokeStyle = options.grid.color;
  401.             ctx.lineJoin = "round";
  402.             ctx.strokeRect(0, 0, plotWidth, plotHeight);
  403.             ctx.restore();
  404.         }
  405.         
  406.         function drawLabels() {
  407.             var i;
  408.             var tick;
  409.             var html = '<div style="font-size:smaller;color:' + options.grid.color + '">';
  410.             // calculate width for labels; to avoid measuring the
  411.             // widths of the labels, we construct fixed-size boxes and
  412.             // put the labels inside them, the fixed-size boxes are
  413.             // easy to mid-align
  414.             var noLabels = 0;
  415.             for (i = 0; i < xaxis.ticks.length; ++i) {
  416.                 if (xaxis.ticks[i].label) {
  417.                     ++noLabels;
  418.                 }
  419.             }
  420.             var xBoxWidth = plotWidth / noLabels;
  421.             
  422.             // do the x-axis
  423.             for (i = 0; i < xaxis.ticks.length; ++i) {
  424.                 tick = xaxis.ticks[i];
  425.                 if (!tick.label)
  426.                     continue;
  427.                 html += '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + options.grid.labelMargin) + 'px;left:' + (plotOffset.left + tHoz(tick.v) - xBoxWidth/2) + 'px;width:' + xBoxWidth + 'px;text-align:center" class="gridLabel">' + tick.label + "</div>";
  428.             }
  429.             
  430.             // do the y-axis
  431.             for (i = 0; i < yaxis.ticks.length; ++i) {
  432.                 tick = yaxis.ticks[i];
  433.                 if (!tick.label || tick.label.length == 0)
  434.                     continue;
  435.                 html += '<div style="position:absolute;top:' + (plotOffset.top + tVert(tick.v) - labelMaxHeight/2) + 'px;left:0;width:' + labelMaxWidth + 'px;text-align:right" class="gridLabel">' + tick.label + "</div>";
  436.             }
  437.             html += '</div>';
  438.             
  439.             target.append(html);
  440.         }
  441.         function fillInSeriesOptions() {
  442.             var i;
  443.             
  444.             // collect what we already got of colors
  445.             var neededColors = series.length;
  446.             var usedColors = [];
  447.             var assignedColors = [];
  448.             for (i = 0; i < series.length; ++i) {
  449.                 var sc = series[i].color;
  450.                 if (sc != null) {
  451.                     --neededColors;
  452.                     if (typeof(sc) == "number")
  453.                         assignedColors.push(sc);
  454.                     else
  455.                         usedColors.push(parseColor(series[i].color));
  456.                 }
  457.             }
  458.             
  459.             // we might need to generate more colors if higher indices
  460.             // are assigned
  461.             for (i = 0; i < assignedColors.length; ++i) {
  462.                 neededColors = Math.max(neededColors, assignedColors[i] + 1);
  463.             }
  464.             // produce colors as needed
  465.             var colors = [];
  466.             var variation = 0;
  467.             i = 0;
  468.             while (colors.length < neededColors) {
  469.                 var c;
  470.                 if (options.colors.length == i) // check degenerate case
  471.                     c = new Color(100, 100, 100);
  472.                 else
  473.                     c = parseColor(options.colors[i]);
  474.                 // vary color if needed
  475.                 var sign = variation % 2 == 1 ? -1 : 1;
  476.                 var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
  477.                 c.scale(factor, factor, factor);
  478.                 // FIXME: if we're getting to close to something else,
  479.                 // we should probably skip this one
  480.                 colors.push(c);
  481.                 
  482.                 ++i;
  483.                 if (i >= options.colors.length) {
  484.                     i = 0;
  485.                     ++variation;
  486.                 }
  487.             }
  488.             // fill in the options
  489.             var colori = 0;
  490.             for (i = 0; i < series.length; ++i) {
  491.                 var s = series[i];
  492.                 // assign colors
  493.                 if (s.color == null) {
  494.                     s.color = colors[colori].toString();
  495.                     ++colori;
  496.                 }
  497.                 else if (typeof(s.color) == "number")
  498.                     s.color = colors[s.color].toString();
  499.                 // copy the rest
  500.                 s.lines = $.extend(true, {}, options.lines, s.lines);
  501.                 s.points = $.extend(true, {}, options.points, s.points);
  502.                 s.bars = $.extend(true, {}, options.bars, s.bars);
  503.                 if (s.shadowSize == null)
  504.                     s.shadowSize = options.shadowSize;
  505.             }
  506.         }
  507.         
  508.         function drawSeries(series) {
  509.             if (series.lines.show || (!series.bars.show && !series.points.show))
  510.                 drawSeriesLines(series);
  511.             if (series.bars.show)
  512.                 drawSeriesBars(series);
  513.             if (series.points.show)
  514.                 drawSeriesPoints(series);
  515.         }
  516.         
  517.         function drawSeriesLines(series) {
  518.             function plotLine(data, offset) {
  519.                 if (data.length < 2)
  520.                     return;
  521.                 var prevx = tHoz(data[0][0]),
  522.                     prevy = tVert(data[0][1]) + offset;
  523.                 ctx.beginPath();
  524.                 ctx.moveTo(prevx, prevy);
  525.                 for (var i = 0; i < data.length - 1; ++i) {
  526.                     var x1 = data[i][0], y1 = data[i][1],
  527.                         x2 = data[i+1][0], y2 = data[i+1][1];
  528.                     // clip with ymin
  529.                     if (y1 <= y2 && y1 < yaxis.min) {
  530.                         if (y2 < yaxis.min)
  531.                             continue;   // line segment is outside
  532.                         // compute new intersection point
  533.                         x1 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  534.                         y1 = yaxis.min;
  535.                     }
  536.                     else if (y2 <= y1 && y2 < yaxis.min) {
  537.                         if (y1 < yaxis.min)
  538.                             continue;
  539.                         x2 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  540.                         y2 = yaxis.min;
  541.                     }
  542.                     // clip with ymax
  543.                     if (y1 >= y2 && y1 > yaxis.max) {
  544.                         if (y2 > yaxis.max)
  545.                             continue;
  546.                         x1 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  547.                         y1 = yaxis.max;
  548.                     }
  549.                     else if (y2 >= y1 && y2 > yaxis.max) {
  550.                         if (y1 > yaxis.max)
  551.                             continue;
  552.                         x2 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  553.                         y2 = yaxis.max;
  554.                     }
  555.                     // clip with xmin
  556.                     if (x1 <= x2 && x1 < xaxis.min) {
  557.                         if (x2 < xaxis.min)
  558.                             continue;
  559.                         y1 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  560.                         x1 = xaxis.min;
  561.                     }
  562.                     else if (x2 <= x1 && x2 < xaxis.min) {
  563.                         if (x1 < xaxis.min)
  564.                             continue;
  565.                         y2 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  566.                         x2 = xaxis.min;
  567.                     }
  568.                     // clip with xmax
  569.                     if (x1 >= x2 && x1 > xaxis.max) {
  570.                         if (x2 > xaxis.max)
  571.                             continue;
  572.                         y1 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  573.                         x1 = xaxis.max;
  574.                     }
  575.                     else if (x2 >= x1 && x2 > xaxis.max) {
  576.                         if (x1 > xaxis.max)
  577.                             continue;
  578.                         y2 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  579.                         x2 = xaxis.max;
  580.                     }
  581.                     if (prevx != tHoz(x1) || prevy != tVert(y1) + offset)
  582.                         ctx.moveTo(tHoz(x1), tVert(y1) + offset);
  583.                     
  584.                     prevx = tHoz(x2);
  585.                     prevy = tVert(y2) + offset;
  586.                     ctx.lineTo(prevx, prevy);
  587.                 }
  588.                 ctx.stroke();
  589.             }
  590.             function plotLineArea(data) {
  591.                 if (data.length < 2)
  592.                     return;
  593.                 var bottom = Math.min(Math.max(0, yaxis.min), yaxis.max);
  594.                 var top, lastX = 0;
  595.                 var first = true;
  596.                 
  597.                 ctx.beginPath();
  598.                 for (var i = 0; i < data.length - 1; ++i) {
  599.                     var x1 = data[i][0], y1 = data[i][1],
  600.                         x2 = data[i+1][0], y2 = data[i+1][1];
  601.                     // clip x values
  602.                     
  603.                     // clip with xmin
  604.                     if (x1 <= x2 && x1 < xaxis.min) {
  605.                         if (x2 < xaxis.min)
  606.                             continue;
  607.                         y1 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  608.                         x1 = xaxis.min;
  609.                     }
  610.                     else if (x2 <= x1 && x2 < xaxis.min) {
  611.                         if (x1 < xaxis.min)
  612.                             continue;
  613.                         y2 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  614.                         x2 = xaxis.min;
  615.                     }
  616.                     // clip with xmax
  617.                     if (x1 >= x2 && x1 > xaxis.max) {
  618.                         if (x2 > xaxis.max)
  619.                             continue;
  620.                         y1 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  621.                         x1 = xaxis.max;
  622.                     }
  623.                     else if (x2 >= x1 && x2 > xaxis.max) {
  624.                         if (x1 > xaxis.max)
  625.                             continue;
  626.                         y2 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  627.                         x2 = xaxis.max;
  628.                     }
  629.                     if (first) {
  630.                         ctx.moveTo(tHoz(x1), tVert(bottom));
  631.                         first = false;
  632.                     }
  633.                     
  634.                     // now first check the case where both is outside
  635.                     if (y1 >= yaxis.max && y2 >= yaxis.max) {
  636.                         ctx.lineTo(tHoz(x1), tVert(yaxis.max));
  637.                         ctx.lineTo(tHoz(x2), tVert(yaxis.max));
  638.                         continue;
  639.                     }
  640.                     else if (y1 <= yaxis.min && y2 <= yaxis.min) {
  641.                         ctx.lineTo(tHoz(x1), tVert(yaxis.min));
  642.                         ctx.lineTo(tHoz(x2), tVert(yaxis.min));
  643.                         continue;
  644.                     }
  645.                     
  646.                     // else it's a bit more complicated, there might
  647.                     // be two rectangles and two triangles we need to fill
  648.                     // in; to find these keep track of the current x values
  649.                     var x1old = x1, x2old = x2;
  650.                     // and clip the y values, without shortcutting
  651.                     
  652.                     // clip with ymin
  653.                     if (y1 <= y2 && y1 < yaxis.min && y2 >= yaxis.min) {
  654.                         x1 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  655.                         y1 = yaxis.min;
  656.                     }
  657.                     else if (y2 <= y1 && y2 < yaxis.min && y1 >= yaxis.min) {
  658.                         x2 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  659.                         y2 = yaxis.min;
  660.                     }
  661.                     // clip with ymax
  662.                     if (y1 >= y2 && y1 > yaxis.max && y2 <= yaxis.max) {
  663.                         x1 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  664.                         y1 = yaxis.max;
  665.                     }
  666.                     else if (y2 >= y1 && y2 > yaxis.max && y1 <= yaxis.max) {
  667.                         x2 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  668.                         y2 = yaxis.max;
  669.                     }
  670.                     // if the x value was changed we got a rectangle
  671.                     // to fill
  672.                     if (x1 != x1old) {
  673.                         if (y1 <= yaxis.min)
  674.                             top = yaxis.min;
  675.                         else
  676.                             top = yaxis.max;
  677.                         
  678.                         ctx.lineTo(tHoz(x1old), tVert(top));
  679.                         ctx.lineTo(tHoz(x1), tVert(top));
  680.                     }
  681.                     
  682.                     // fill the triangles
  683.                     ctx.lineTo(tHoz(x1), tVert(y1));
  684.                     ctx.lineTo(tHoz(x2), tVert(y2));
  685.                     // fill the other rectangle if it's there
  686.                     if (x2 != x2old) {
  687.                         if (y2 <= yaxis.min)
  688.                             top = yaxis.min;
  689.                         else
  690.                             top = yaxis.max;
  691.                         
  692.                         ctx.lineTo(tHoz(x2old), tVert(top));
  693.                         ctx.lineTo(tHoz(x2), tVert(top));
  694.                     }
  695.                     lastX = Math.max(x2, x2old);
  696.                 }
  697.                 /*
  698.                 ctx.beginPath();
  699.                 ctx.moveTo(tHoz(data[0][0]), tVert(0));
  700.                 for (var i = 0; i < data.length; i++) {
  701.                     ctx.lineTo(tHoz(data[i][0]), tVert(data[i][1]));
  702.                 }
  703.                 ctx.lineTo(tHoz(data[data.length - 1][0]), tVert(0));*/
  704.                 ctx.lineTo(tHoz(lastX), tVert(bottom));
  705.                 ctx.fill();
  706.             }
  707.             
  708.             ctx.save();
  709.             ctx.translate(plotOffset.left, plotOffset.top);
  710.             ctx.lineJoin = "round";
  711.             var lw = series.lines.lineWidth;
  712.             var sw = series.shadowSize;
  713.             // FIXME: consider another form of shadow when filling is turned on
  714.             if (sw > 0) {
  715.                 // draw shadow in two steps
  716.                 ctx.lineWidth = sw / 2;
  717.                 ctx.strokeStyle = "rgba(0,0,0,0.1)";
  718.                 plotLine(series.data, lw/2 + sw/2 + ctx.lineWidth/2);
  719.                 ctx.lineWidth = sw / 2;
  720.                 ctx.strokeStyle = "rgba(0,0,0,0.2)";
  721.                 plotLine(series.data, lw/2 + ctx.lineWidth/2);
  722.             }
  723.             ctx.lineWidth = lw;
  724.             ctx.strokeStyle = series.color;
  725.             if (series.lines.fill) {
  726.                 ctx.fillStyle = series.lines.fillColor != null ? series.lines.fillColor : parseColor(series.color).scale(null, null, null, 0.4).toString();
  727.                 plotLineArea(series.data, 0);
  728.             }
  729.             plotLine(series.data, 0);
  730.             ctx.restore();
  731.         }
  732.         function drawSeriesPoints(series) {
  733.             function plotPoints(data, radius, fill) {
  734.                 for (var i = 0; i < data.length; ++i) {
  735.                     var x = data[i][0], y = data[i][1];
  736.                     if (x < xaxis.min || x > xaxis.max || y < yaxis.min || y > yaxis.max)
  737.                         continue;
  738.                     
  739.                     ctx.beginPath();
  740.                     ctx.arc(tHoz(x), tVert(y), radius, 0, 2 * Math.PI, true);
  741.                     if (fill)
  742.                         ctx.fill();
  743.                     ctx.stroke();
  744.                 }
  745.             }
  746.             function plotPointShadows(data, offset, radius) {
  747.                 for (var i = 0; i < data.length; ++i) {
  748.                     var x = data[i][0], y = data[i][1];
  749.                     if (x < xaxis.min || x > xaxis.max || y < yaxis.min || y > yaxis.max)
  750.                         continue;
  751.                     ctx.beginPath();
  752.                     ctx.arc(tHoz(x), tVert(y) + offset, radius, 0, Math.PI, false);
  753.                     ctx.stroke();
  754.                 }
  755.             }
  756.             
  757.             ctx.save();
  758.             ctx.translate(plotOffset.left, plotOffset.top);
  759.             var lw = series.lines.lineWidth;
  760.             var sw = series.shadowSize;
  761.             if (sw > 0) {
  762.                 // draw shadow in two steps
  763.                 ctx.lineWidth = sw / 2;
  764.                 ctx.strokeStyle = "rgba(0,0,0,0.1)";
  765.                 plotPointShadows(series.data, sw/2 + ctx.lineWidth/2, series.points.radius);
  766.                 ctx.lineWidth = sw / 2;
  767.                 ctx.strokeStyle = "rgba(0,0,0,0.2)";
  768.                 plotPointShadows(series.data, ctx.lineWidth/2, series.points.radius);
  769.             }
  770.             ctx.lineWidth = series.points.lineWidth;
  771.             ctx.strokeStyle = series.color;
  772.             ctx.fillStyle = series.points.fillColor != null ? series.points.fillColor : series.color;
  773.             plotPoints(series.data, series.points.radius, series.points.fill);
  774.             ctx.restore();
  775.         }
  776.         function drawSeriesBars(series) {
  777.             function plotBars(data, barWidth, offset, fill) {
  778.                 if (data.length < 2)
  779.                     return;
  780.                 for (var i = 0; i < data.length; i++) {
  781.                     var x = data[i][0], y = data[i][1];
  782.                     var drawLeft = true, drawTop = true, drawRight = true;
  783.                     var left = x, right = x + barWidth, bottom = 0, top = y;
  784.                     if (right < xaxis.min || left > xaxis.max || top < yaxis.min || bottom > yaxis.max)
  785.                         continue;
  786.                     // clip
  787.                     if (left < xaxis.min) {
  788.                         left = xaxis.min;
  789.                         drawLeft = false;
  790.                     }
  791.                     if (right > xaxis.max) {
  792.                         right = xaxis.max;
  793.                         drawRight = false;
  794.                     }
  795.                     if (bottom < yaxis.min)
  796.                         bottom = yaxis.min;
  797.                     if (top > yaxis.max) {
  798.                         top = yaxis.max;
  799.                         drawTop = false;
  800.                     }
  801.                     // fill the bar
  802.                     if (fill) {
  803.                         ctx.beginPath();
  804.                         ctx.moveTo(tHoz(left), tVert(bottom) + offset);
  805.                         ctx.lineTo(tHoz(left), tVert(top) + offset);
  806.                         ctx.lineTo(tHoz(right), tVert(top) + offset);
  807.                         ctx.lineTo(tHoz(right), tVert(bottom) + offset);
  808.                         ctx.fill();
  809.                     }
  810.                     // draw outline
  811.                     if (drawLeft || drawRight || drawTop) {
  812.                         ctx.beginPath();
  813.                         ctx.moveTo(tHoz(left), tVert(bottom) + offset);
  814.                         if (drawLeft)
  815.                             ctx.lineTo(tHoz(left), tVert(top) + offset);
  816.                         else
  817.                             ctx.moveTo(tHoz(left), tVert(top) + offset);
  818.                         if (drawTop)
  819.                             ctx.lineTo(tHoz(right), tVert(top) + offset);
  820.                         else
  821.                             ctx.moveTo(tHoz(right), tVert(top) + offset);
  822.                         if (drawRight)
  823.                             ctx.lineTo(tHoz(right), tVert(bottom) + offset);
  824.                         else
  825.                             ctx.moveTo(tHoz(right), tVert(bottom) + offset);
  826.                         ctx.stroke();
  827.                     }
  828.                 }
  829.             }
  830.             ctx.save();
  831.             ctx.translate(plotOffset.left, plotOffset.top);
  832.             ctx.lineJoin = "round";
  833.             var bw = series.bars.barWidth;
  834.             var lw = Math.min(series.bars.lineWidth, bw);
  835.             // FIXME: figure out a way to add shadows
  836.             /*
  837.             var sw = series.shadowSize;
  838.             if (sw > 0) {
  839.                 // draw shadow in two steps
  840.                 ctx.lineWidth = sw / 2;
  841.                 ctx.strokeStyle = "rgba(0,0,0,0.1)";
  842.                 plotBars(series.data, bw, lw/2 + sw/2 + ctx.lineWidth/2, false);
  843.                 ctx.lineWidth = sw / 2;
  844.                 ctx.strokeStyle = "rgba(0,0,0,0.2)";
  845.                 plotBars(series.data, bw, lw/2 + ctx.lineWidth/2, false);
  846.             }*/
  847.             ctx.lineWidth = lw;
  848.             ctx.strokeStyle = series.color;
  849.             if (series.bars.fill) {
  850.                 ctx.fillStyle = series.bars.fillColor != null ? series.bars.fillColor : parseColor(series.color).scale(null, null, null, 0.4).toString();
  851.             }
  852.             plotBars(series.data, bw, 0, series.bars.fill);
  853.             ctx.restore();
  854.         }
  855.         function insertLegend() {
  856.             if (!options.legend.show)
  857.                 return;
  858.             
  859.             var fragments = [];
  860.             var rowStarted = false;
  861.             for (i = 0; i < series.length; ++i) {
  862.                 if (!series[i].label)
  863.                     continue;
  864.                 
  865.                 if (i % options.legend.noColumns == 0) {
  866.                     if (rowStarted)
  867.                         fragments.push('</tr>');
  868.                     fragments.push('<tr>');
  869.                     rowStarted = true;
  870.                 }
  871.                 var label = series[i].label;
  872.                 if (options.legend.labelFormatter != null)
  873.                     label = options.legend.labelFormatter(label);
  874.                 
  875.                 fragments.push(
  876.                     '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:14px;height:10px;background-color:' + series[i].color + '"></div></div></td>' +
  877.                     '<td class="legendLabel">' + label + '</td>');
  878.             }
  879.             if (rowStarted)
  880.                 fragments.push('</tr>');
  881.             
  882.             if (fragments.length > 0) {
  883.                 var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
  884.                 if (options.legend.container != null)
  885.                     options.legend.container.append(table);
  886.                 else {
  887.                     var pos = "";
  888.                     var p = options.legend.position, m = options.legend.margin;
  889.                     if (p.charAt(0) == "n")
  890.                         pos += 'top:' + (m + plotOffset.top) + 'px;';
  891.                     else if (p.charAt(0) == "s")
  892.                         pos += 'bottom:' + (m + plotOffset.bottom) + 'px;';
  893.                     if (p.charAt(1) == "e")
  894.                         pos += 'right:' + (m + plotOffset.right) + 'px;';
  895.                     else if (p.charAt(1) == "w")
  896.                         pos += 'left:' + (m + plotOffset.bottom) + 'px;';
  897.                     var div = $('<div class="legend" style="position:absolute;z-index:2;' + pos +'">' + table + '</div>').appendTo(target);
  898.                     if (options.legend.backgroundOpacity != 0.0) {
  899.                         // put in the transparent background
  900.                         // separately to avoid blended labels and
  901.                         // label boxes
  902.                         var c = options.legend.backgroundColor;
  903.                         if (c == null) {
  904.                             var tmp;
  905.                             if (options.grid.backgroundColor != null)
  906.                                 tmp = options.grid.backgroundColor;
  907.                             else
  908.                                 tmp = extractColor(div);
  909.                             c = parseColor(tmp).adjust(null, null, null, 1).toString();
  910.                         }
  911.                         $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').appendTo(target).css('opacity', options.legend.backgroundOpacity);
  912.                         
  913.                     }
  914.                 }
  915.             }
  916.         }
  917.         var lastMousePos = { pageX: null, pageY: null };
  918.         var selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} };
  919.         var prevSelection = null;
  920.         var selectionInterval = null;
  921.         var ignoreClick = false;
  922.         
  923.         function onMouseMove(ev) {
  924.             // FIXME: temp. work-around until jQuery bug 1871 is fixed
  925.             var e = ev || window.event;
  926.             if (e.pageX == null && e.clientX != null) {
  927.                 var de = document.documentElement, b = document.body;
  928.                 lastMousePos.pageX = e.clientX + (de && de.scrollLeft || b.scrollLeft || 0);
  929.                 lastMousePos.pageY = e.clientY + (de && de.scrollTop || b.scrollTop || 0);
  930.             }
  931.             else {
  932.                 lastMousePos.pageX = e.pageX;
  933.                 lastMousePos.pageY = e.pageY;
  934.             }
  935.         }
  936.         
  937.         function onMouseDown(e) {
  938.             if (e.which != 1)  // only accept left-click
  939.                 return;
  940.             
  941.             setSelectionPos(selection.first, e);
  942.                 
  943.             if (selectionInterval != null)
  944.                 clearInterval(selectionInterval);
  945.             lastMousePos.pageX = null;
  946.             selectionInterval = setInterval(updateSelectionOnMouseMove, 200);
  947.             $(document).one("mouseup", onSelectionMouseUp);
  948.         }
  949.         function onClick(e) {
  950.             if (ignoreClick) {
  951.                 ignoreClick = false;
  952.                 return;
  953.             }
  954.             
  955.             var offset = $(overlay).offset();
  956.             var pos = {};
  957.             pos.x = e.pageX - offset.left - plotOffset.left;
  958.             pos.x = xaxis.min + pos.x / hozScale;
  959.             pos.y = e.pageY - offset.top - plotOffset.top;
  960.             pos.y = yaxis.max - pos.y / vertScale;
  961.             target.trigger("plotclick", [ pos ]);
  962.         }
  963.         
  964.         function triggerSelectedEvent() {
  965.             var x1, x2, y1, y2;
  966.             if (selection.first.x <= selection.second.x) {
  967.                 x1 = selection.first.x;
  968.                 x2 = selection.second.x;
  969.             }
  970.             else {
  971.                 x1 = selection.second.x;
  972.                 x2 = selection.first.x;
  973.             }
  974.             if (selection.first.y >= selection.second.y) {
  975.                 y1 = selection.first.y;
  976.                 y2 = selection.second.y;
  977.             }
  978.             else {
  979.                 y1 = selection.second.y;
  980.                 y2 = selection.first.y;
  981.             }
  982.             
  983.             x1 = xaxis.min + x1 / hozScale;
  984.             x2 = xaxis.min + x2 / hozScale;
  985.             y1 = yaxis.max - y1 / vertScale;
  986.             y2 = yaxis.max - y2 / vertScale;
  987.             target.trigger("selected", [ { x1: x1, y1: y1, x2: x2, y2: y2 } ]);
  988.         }
  989.         
  990.         function onSelectionMouseUp(e) {
  991.             if (selectionInterval != null) {
  992.                 clearInterval(selectionInterval);
  993.                 selectionInterval = null;
  994.             }
  995.             setSelectionPos(selection.second, e);
  996.             clearSelection();
  997.             if (!selectionIsSane() || e.which != 1)
  998.                 return false;
  999.             
  1000.             drawSelection();
  1001.             triggerSelectedEvent();
  1002.             ignoreClick = true;
  1003.             return false;
  1004.         }
  1005.         function setSelectionPos(pos, e) {
  1006.             var offset = $(overlay).offset();
  1007.             if (options.selection.mode == "y") {
  1008.                 if (pos == selection.first)
  1009.                     pos.x = 0;
  1010.                 else
  1011.                     pos.x = plotWidth;
  1012.             }
  1013.             else {
  1014.                 pos.x = e.pageX - offset.left - plotOffset.left;
  1015.                 pos.x = Math.min(Math.max(0, pos.x), plotWidth);
  1016.             }
  1017.             if (options.selection.mode == "x") {
  1018.                 if (pos == selection.first)
  1019.                     pos.y = 0;
  1020.                 else
  1021.                     pos.y = plotHeight;
  1022.             }
  1023.             else {
  1024.         pos.y = e.pageY - offset.top - plotOffset.top;
  1025.                 pos.y = Math.min(Math.max(0, pos.y), plotHeight);
  1026.             }
  1027.         }
  1028.         
  1029.         function updateSelectionOnMouseMove() {
  1030.             if (lastMousePos.pageX == null)
  1031.                 return;
  1032.             
  1033.             setSelectionPos(selection.second, lastMousePos);
  1034.             clearSelection();
  1035.             if (selectionIsSane())
  1036.                 drawSelection();
  1037.         }
  1038.         function clearSelection() {
  1039.             if (prevSelection == null)
  1040.                 return;
  1041.             var x = Math.min(prevSelection.first.x, prevSelection.second.x),
  1042.                 y = Math.min(prevSelection.first.y, prevSelection.second.y),
  1043.                 w = Math.abs(prevSelection.second.x - prevSelection.first.x),
  1044.                 h = Math.abs(prevSelection.second.y - prevSelection.first.y);
  1045.             
  1046.             octx.clearRect(x + plotOffset.left - octx.lineWidth,
  1047.                            y + plotOffset.top - octx.lineWidth,
  1048.                            w + octx.lineWidth*2,
  1049.                            h + octx.lineWidth*2);
  1050.             
  1051.             prevSelection = null;
  1052.         }
  1053.         
  1054.         function setSelection(area) {
  1055.             clearSelection();
  1056.             
  1057.             if (options.selection.mode == "x") {
  1058.                 selection.first.y = 0;
  1059.                 selection.second.y = plotHeight;
  1060.             }
  1061.             else {
  1062.                 selection.first.y = (yaxis.max - area.y1) * vertScale;
  1063.                 selection.second.y = (yaxis.max - area.y2) * vertScale;
  1064.             }
  1065.             if (options.selection.mode == "y") {
  1066.                 selection.first.x = 0;
  1067.                 selection.second.x = plotWidth;
  1068.             }
  1069.             else {
  1070.                 selection.first.x = (area.x1 - xaxis.min) * hozScale;
  1071.                 selection.second.x = (area.x2 - xaxis.min) * hozScale;
  1072.             }
  1073.             drawSelection();
  1074.             triggerSelectedEvent();
  1075.         }
  1076.         
  1077.         function drawSelection() {
  1078.             if (prevSelection != null &&
  1079.                 selection.first.x == prevSelection.first.x &&
  1080.                 selection.first.y == prevSelection.first.y && 
  1081.                 selection.second.x == prevSelection.second.x &&
  1082.                 selection.second.y == prevSelection.second.y)
  1083.                 return;
  1084.             
  1085.             octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString();
  1086.             octx.lineWidth = 1;
  1087.             ctx.lineJoin = "round";
  1088.             octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString();
  1089.             prevSelection = { first:  { x: selection.first.x,
  1090.                                         y: selection.first.y },
  1091.                               second: { x: selection.second.x,
  1092.                                         y: selection.second.y } };
  1093.             var x = Math.min(selection.first.x, selection.second.x),
  1094.                 y = Math.min(selection.first.y, selection.second.y),
  1095.                 w = Math.abs(selection.second.x - selection.first.x),
  1096.                 h = Math.abs(selection.second.y - selection.first.y);
  1097.             
  1098.             octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h);
  1099.             octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h);
  1100.         }
  1101.         function selectionIsSane() {
  1102.             var minSize = 5;
  1103.             return Math.abs(selection.second.x - selection.first.x) >= minSize &&
  1104.                 Math.abs(selection.second.y - selection.first.y) >= minSize;
  1105.         }
  1106.     }
  1107.     
  1108.     $.plot = function(target, data, options) {
  1109.         var plot = new Plot(target, data, options);
  1110.         /*var t0 = new Date();     
  1111.         var t1 = new Date();
  1112. var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime())
  1113. if (window.console)
  1114.             console.log(tstr);
  1115. else
  1116.     alert(tstr);*/
  1117.         return plot;
  1118.     };
  1119.     function getMagnitude(x) {
  1120.         return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));
  1121.     }
  1122.        
  1123.     // color helpers, inspiration from the jquery color animation
  1124.     // plugin by John Resig
  1125.     function Color (r, g, b, a) {
  1126.        
  1127.         var rgba = ['r','g','b','a'];
  1128.         var x = 4; //rgba.length
  1129.        
  1130.         while (-1<--x) {
  1131.             this[rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
  1132.         }
  1133.        
  1134.         this.toString = function() {
  1135.             if (this.a >= 1.0) {
  1136.                 return "rgb("+[this.r,this.g,this.b].join(",")+")";
  1137.             } else {
  1138.                 return "rgba("+[this.r,this.g,this.b,this.a].join(",")+")";
  1139.             }
  1140.         };
  1141.         this.scale = function(rf, gf, bf, af) {
  1142.             x = 4; //rgba.length
  1143.             while (-1<--x) {
  1144.                 if (arguments[x] != null)
  1145.                     this[rgba[x]] *= arguments[x];
  1146.             }
  1147.             return this.normalize();
  1148.         };
  1149.         this.adjust = function(rd, gd, bd, ad) {
  1150.             x = 4; //rgba.length
  1151.             while (-1<--x) {
  1152.                 if (arguments[x] != null)
  1153.                     this[rgba[x]] += arguments[x];
  1154.             }
  1155.             return this.normalize();
  1156.         };
  1157.         this.clone = function() {
  1158.             return new Color(this.r, this.b, this.g, this.a);
  1159.         };
  1160.         var limit = function(val,minVal,maxVal) {
  1161.             return Math.max(Math.min(val, maxVal), minVal);
  1162.         };
  1163.         this.normalize = function() {
  1164.             this.r = limit(parseInt(this.r), 0, 255);
  1165.             this.g = limit(parseInt(this.g), 0, 255);
  1166.             this.b = limit(parseInt(this.b), 0, 255);
  1167.             this.a = limit(this.a, 0, 1);
  1168.             return this;
  1169.         };
  1170.         this.normalize();
  1171.     }
  1172.     
  1173.     var lookupColors = {
  1174. aqua:[0,255,255],
  1175. azure:[240,255,255],
  1176. beige:[245,245,220],
  1177. black:[0,0,0],
  1178. blue:[0,0,255],
  1179. brown:[165,42,42],
  1180. cyan:[0,255,255],
  1181. darkblue:[0,0,139],
  1182. darkcyan:[0,139,139],
  1183. darkgrey:[169,169,169],
  1184. darkgreen:[0,100,0],
  1185. darkkhaki:[189,183,107],
  1186. darkmagenta:[139,0,139],
  1187. darkolivegreen:[85,107,47],
  1188. darkorange:[255,140,0],
  1189. darkorchid:[153,50,204],
  1190. darkred:[139,0,0],
  1191. darksalmon:[233,150,122],
  1192. darkviolet:[148,0,211],
  1193. fuchsia:[255,0,255],
  1194. gold:[255,215,0],
  1195. green:[0,128,0],
  1196. indigo:[75,0,130],
  1197. khaki:[240,230,140],
  1198. lightblue:[173,216,230],
  1199. lightcyan:[224,255,255],
  1200. lightgreen:[144,238,144],
  1201. lightgrey:[211,211,211],
  1202. lightpink:[255,182,193],
  1203. lightyellow:[255,255,224],
  1204. lime:[0,255,0],
  1205. magenta:[255,0,255],
  1206. maroon:[128,0,0],
  1207. navy:[0,0,128],
  1208. olive:[128,128,0],
  1209. orange:[255,165,0],
  1210. pink:[255,192,203],
  1211. purple:[128,0,128],
  1212. violet:[128,0,128],
  1213. red:[255,0,0],
  1214. silver:[192,192,192],
  1215. white:[255,255,255],
  1216. yellow:[255,255,0]
  1217.     };    
  1218.     function extractColor(element) {
  1219.         var color, elem = element;
  1220. do {
  1221.     color = elem.css("background-color").toLowerCase();
  1222.             // keep going until we find an element that has color, or
  1223.             // we hit the body
  1224.     if (color != '' && color != 'transparent')
  1225. break;
  1226.             elem = elem.parent();
  1227. } while (!$.nodeName(elem.get(0), "body"));
  1228.         // catch Safari's way of signalling transparent
  1229.         if (color == "rgba(0, 0, 0, 0)") 
  1230.             return "transparent";
  1231.         
  1232.         return color;
  1233.     }
  1234.     
  1235.     // parse string, returns Color
  1236.     function parseColor(str) {
  1237.         // Some named colors to work with
  1238.         // From Interface by Stefan Petre
  1239.         // http://interface.eyecon.ro/
  1240. var result;
  1241. // Look for rgb(num,num,num)
  1242. if (result = /rgb(s*([0-9]{1,3})s*,s*([0-9]{1,3})s*,s*([0-9]{1,3})s*)/.exec(str))
  1243.     return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]));
  1244. // Look for rgba(num,num,num,num)
  1245. if (result = /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(str))
  1246.     return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4]));
  1247.         
  1248. // Look for rgb(num%,num%,num%)
  1249. if (result = /rgb(s*([0-9]+(?:.[0-9]+)?)%s*,s*([0-9]+(?:.[0-9]+)?)%s*,s*([0-9]+(?:.[0-9]+)?)%s*)/.exec(str))
  1250.     return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
  1251. // Look for rgba(num%,num%,num%,num)
  1252. if (result = /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(str))
  1253.     return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
  1254.         
  1255. // Look for #a0b1c2
  1256. if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
  1257.     return new Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16));
  1258. // Look for #fff
  1259. if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
  1260.     return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));
  1261. // Otherwise, we're most likely dealing with a named color
  1262.         var name = jQuery.trim(str).toLowerCase();
  1263.         if (name == "transparent")
  1264.             return new Color(255, 255, 255, 0);
  1265.         else {
  1266.             result = lookupColors[name];
  1267.     return new Color(result[0], result[1], result[2]);
  1268.         }
  1269.     }
  1270. })(jQuery);