Warning, file /office/tellico/xslt/report-templates/jquery.flot.js was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* Javascript plotting library for jQuery, v. 0.5. 0002 * 0003 * Released under the MIT license by IOLA, December 2007. 0004 * 0005 */ 0006 0007 (function($) { 0008 function Plot(target, data_, options_, plugins) { 0009 // data is on the form: 0010 // [ series1, series2 ... ] 0011 // where series is either just the data as [ [x1, y1], [x2, y2], ... ] 0012 // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } 0013 0014 var series = [], 0015 options = { 0016 // the color theme used for graphs 0017 colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], 0018 legend: { 0019 show: true, 0020 noColumns: 1, // number of colums in legend table 0021 labelFormatter: null, // fn: string -> string 0022 labelBoxBorderColor: "#ccc", // border color for the little label boxes 0023 container: null, // container (as jQuery object) to put legend in, null means default on top of graph 0024 position: "ne", // position of default legend container within plot 0025 margin: 5, // distance from grid edge to default legend container within plot 0026 backgroundColor: null, // null means auto-detect 0027 backgroundOpacity: 0.85 // set to 0 to avoid background 0028 }, 0029 xaxis: { 0030 mode: null, // null or "time" 0031 min: null, // min. value to show, null means set automatically 0032 max: null, // max. value to show, null means set automatically 0033 autoscaleMargin: null, // margin in % to add if auto-setting min/max 0034 ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks 0035 tickFormatter: null, // fn: number -> string 0036 labelWidth: null, // size of tick labels in pixels 0037 labelHeight: null, 0038 0039 // mode specific options 0040 tickDecimals: null, // no. of decimals, null means auto 0041 tickSize: null, // number or [number, "unit"] 0042 minTickSize: null, // number or [number, "unit"] 0043 monthNames: null, // list of names of months 0044 timeformat: null // format string to use 0045 }, 0046 yaxis: { 0047 autoscaleMargin: 0.02 0048 }, 0049 x2axis: { 0050 autoscaleMargin: null 0051 }, 0052 y2axis: { 0053 autoscaleMargin: 0.02 0054 }, 0055 series: { 0056 points: { 0057 show: false, 0058 radius: 3, 0059 lineWidth: 2, // in pixels 0060 fill: true, 0061 fillColor: "#ffffff" 0062 }, 0063 lines: { 0064 // we don't put in show: false so we can see 0065 // whether lines were actively disabled 0066 lineWidth: 2, // in pixels 0067 fill: false, 0068 fillColor: null, 0069 steps: false 0070 }, 0071 bars: { 0072 show: false, 0073 lineWidth: 2, // in pixels 0074 barWidth: 1, // in units of the x axis 0075 fill: true, 0076 fillColor: null, 0077 align: "left", // or "center" 0078 horizontal: false // when horizontal, left is now top 0079 }, 0080 shadowSize: 3 0081 }, 0082 grid: { 0083 show: true, 0084 color: "#545454", // primary color used for outline and labels 0085 backgroundColor: null, // null for transparent, else color 0086 tickColor: "#dddddd", // color used for the ticks 0087 labelMargin: 5, // in pixels 0088 borderWidth: 2, // in pixels 0089 borderColor: null, // set if different from the grid color 0090 markings: null, // array of ranges or fn: axes -> array of ranges 0091 markingsColor: "#f4f4f4", 0092 markingsLineWidth: 2, 0093 // interactive stuff 0094 clickable: false, 0095 hoverable: false, 0096 autoHighlight: true, // highlight in case mouse is near 0097 mouseActiveRadius: 10 // how far the mouse can be away to activate an item 0098 }, 0099 selection: { 0100 mode: null, // one of null, "x", "y" or "xy" 0101 color: "#e8cfac" 0102 } 0103 }, 0104 canvas = null, // the canvas for the plot itself 0105 overlay = null, // canvas for interactive stuff on top of plot 0106 eventHolder = null, // jQuery object that events should be bound to 0107 ctx = null, octx = null, 0108 axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} }, 0109 plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, 0110 canvasWidth = 0, canvasHeight = 0, 0111 plotWidth = 0, plotHeight = 0, 0112 hooks = { 0113 processOptions: [], 0114 processRawData: [], 0115 processDatapoints: [], 0116 draw: [], 0117 bindEvents: [], 0118 drawOverlay: [] 0119 }, 0120 plot = this, 0121 // dedicated to storing data for buggy standard compliance cases 0122 workarounds = {}; 0123 0124 // public functions 0125 plot.setData = setData; 0126 plot.setupGrid = setupGrid; 0127 plot.draw = draw; 0128 plot.clearSelection = clearSelection; 0129 plot.setSelection = setSelection; 0130 plot.getSelection = getSelection; 0131 plot.getCanvas = function() { return canvas; }; 0132 plot.getPlotOffset = function() { return plotOffset; }; 0133 plot.width = function () { return plotWidth; }; 0134 plot.height = function () { return plotHeight; }; 0135 plot.offset = function () { 0136 var o = eventHolder.offset(); 0137 o.left += plotOffset.left; 0138 o.top += plotOffset.top; 0139 return o; 0140 }; 0141 plot.getData = function() { return series; }; 0142 plot.getAxes = function() { return axes; }; 0143 plot.getOptions = function() { return options; }; 0144 plot.highlight = highlight; 0145 plot.unhighlight = unhighlight; 0146 plot.triggerRedrawOverlay = triggerRedrawOverlay; 0147 plot.pointOffset = function(point) { 0148 return { left: parseInt(axisSpecToRealAxis(point, "xaxis").p2c(+point.x) + plotOffset.left), 0149 top: parseInt(axisSpecToRealAxis(point, "yaxis").p2c(+point.y) + plotOffset.top) }; 0150 }; 0151 0152 0153 // public attributes 0154 plot.hooks = hooks; 0155 0156 // initialize 0157 initPlugins(plot); 0158 parseOptions(options_); 0159 constructCanvas(); 0160 setData(data_); 0161 setupGrid(); 0162 draw(); 0163 bindEvents(); 0164 0165 0166 function executeHooks(hook, args) { 0167 args = [plot].concat(args); 0168 for (var i = 0; i < hook.length; ++i) 0169 hook[i].apply(this, args); 0170 } 0171 0172 function initPlugins() { 0173 for (var i = 0; i < plugins.length; ++i) { 0174 var p = plugins[i]; 0175 p.init(plot); 0176 if (p.options) 0177 $.extend(true, options, p.options); 0178 } 0179 } 0180 0181 function parseOptions(opts) { 0182 $.extend(true, options, opts); 0183 if (options.grid.borderColor == null) 0184 options.grid.borderColor = options.grid.color; 0185 // backwards compatibility, to be removed in future 0186 if (options.xaxis.noTicks && options.xaxis.ticks == null) 0187 options.xaxis.ticks = options.xaxis.noTicks; 0188 if (options.yaxis.noTicks && options.yaxis.ticks == null) 0189 options.yaxis.ticks = options.yaxis.noTicks; 0190 if (options.grid.coloredAreas) 0191 options.grid.markings = options.grid.coloredAreas; 0192 if (options.grid.coloredAreasColor) 0193 options.grid.markingsColor = options.grid.coloredAreasColor; 0194 if (options.lines) 0195 $.extend(true, options.series.lines, options.lines); 0196 if (options.points) 0197 $.extend(true, options.series.points, options.points); 0198 if (options.bars) 0199 $.extend(true, options.series.bars, options.bars); 0200 if (options.shadowSize) 0201 options.series.shadowSize = options.shadowSize; 0202 0203 executeHooks(hooks.processOptions, [options]); 0204 } 0205 0206 function setData(d) { 0207 series = parseData(d); 0208 fillInSeriesOptions(); 0209 processData(); 0210 } 0211 0212 function parseData(d) { 0213 var res = []; 0214 for (var i = 0; i < d.length; ++i) { 0215 var s = $.extend(true, {}, options.series); 0216 0217 if (d[i].data) { 0218 s.data = d[i].data; // move the data instead of deep-copy 0219 delete d[i].data; 0220 0221 $.extend(true, s, d[i]); 0222 0223 d[i].data = s.data; 0224 } 0225 else 0226 s.data = d[i]; 0227 res.push(s); 0228 } 0229 0230 return res; 0231 } 0232 0233 function axisSpecToRealAxis(obj, attr) { 0234 var a = obj[attr]; 0235 if (!a || a == 1) 0236 return axes[attr]; 0237 if (typeof a == "number") 0238 return axes[attr.charAt(0) + a + attr.slice(1)]; 0239 return a; // assume it's OK 0240 } 0241 0242 function fillInSeriesOptions() { 0243 var i; 0244 0245 // collect what we already got of colors 0246 var neededColors = series.length, 0247 usedColors = [], 0248 assignedColors = []; 0249 for (i = 0; i < series.length; ++i) { 0250 var sc = series[i].color; 0251 if (sc != null) { 0252 --neededColors; 0253 if (typeof sc == "number") 0254 assignedColors.push(sc); 0255 else 0256 usedColors.push(parseColor(series[i].color)); 0257 } 0258 } 0259 0260 // we might need to generate more colors if higher indices 0261 // are assigned 0262 for (i = 0; i < assignedColors.length; ++i) { 0263 neededColors = Math.max(neededColors, assignedColors[i] + 1); 0264 } 0265 0266 // produce colors as needed 0267 var colors = [], variation = 0; 0268 i = 0; 0269 while (colors.length < neededColors) { 0270 var c; 0271 if (options.colors.length == i) // check degenerate case 0272 c = new Color(100, 100, 100); 0273 else 0274 c = parseColor(options.colors[i]); 0275 0276 // vary color if needed 0277 var sign = variation % 2 == 1 ? -1 : 1; 0278 var factor = 1 + sign * Math.ceil(variation / 2) * 0.2; 0279 c.scale(factor, factor, factor); 0280 0281 // FIXME: if we're getting to close to something else, 0282 // we should probably skip this one 0283 colors.push(c); 0284 0285 ++i; 0286 if (i >= options.colors.length) { 0287 i = 0; 0288 ++variation; 0289 } 0290 } 0291 0292 // fill in the options 0293 var colori = 0, s; 0294 for (i = 0; i < series.length; ++i) { 0295 s = series[i]; 0296 0297 // assign colors 0298 if (s.color == null) { 0299 s.color = colors[colori].toString(); 0300 ++colori; 0301 } 0302 else if (typeof s.color == "number") 0303 s.color = colors[s.color].toString(); 0304 0305 // turn on lines automatically in case nothing is set 0306 if (s.lines.show == null && !s.bars.show && !s.points.show) 0307 s.lines.show = true; 0308 0309 // setup axes 0310 s.xaxis = axisSpecToRealAxis(s, "xaxis"); 0311 s.yaxis = axisSpecToRealAxis(s, "yaxis"); 0312 } 0313 } 0314 0315 function processData() { 0316 var topSentry = Number.POSITIVE_INFINITY, 0317 bottomSentry = Number.NEGATIVE_INFINITY, 0318 i, j, k, m, length, 0319 s, points, ps, x, y, axis; 0320 0321 for (axis in axes) { 0322 axes[axis].datamin = topSentry; 0323 axes[axis].datamax = bottomSentry; 0324 axes[axis].min = options[axis].min; 0325 axes[axis].max = options[axis].max; 0326 axes[axis].used = false; 0327 } 0328 0329 function updateAxis(axis, min, max) { 0330 if (min < axis.datamin) 0331 axis.datamin = min; 0332 if (max > axis.datamax) 0333 axis.datamax = max; 0334 } 0335 0336 for (i = 0; i < series.length; ++i) { 0337 s = series[i]; 0338 s.datapoints = { points: [] }; 0339 0340 executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); 0341 } 0342 0343 // first pass: clean and copy data 0344 for (i = 0; i < series.length; ++i) { 0345 s = series[i]; 0346 0347 if (s.datapoints.pointsize != null) 0348 continue; // already filled in 0349 0350 var data = s.data, format = [], p; 0351 0352 // determine the point size 0353 if (s.bars.show) { 0354 s.datapoints.pointsize = 3; 0355 format.push({ d: 0 }); 0356 } 0357 else 0358 s.datapoints.pointsize = 2; 0359 0360 /* 0361 // examine data to find out how to copy 0362 for (j = 0; j < data.length; ++j) { 0363 }*/ 0364 0365 ps = s.datapoints.pointsize; 0366 points = s.datapoints.points; 0367 0368 insertSteps = s.lines.show && s.lines.steps; 0369 s.xaxis.used = s.yaxis.used = true; 0370 0371 for (j = k = 0; j < data.length; ++j, k += ps) { 0372 p = data[j]; 0373 0374 if (p != null) { 0375 x = p[0]; 0376 y = p[1]; 0377 } 0378 else 0379 y = x = null; 0380 0381 if (x != null) { 0382 x = +x; // convert to number 0383 if (isNaN(x)) 0384 x = null; 0385 } 0386 0387 if (y != null) { 0388 y = +y; // convert to number 0389 if (isNaN(y)) 0390 y = null; 0391 } 0392 0393 // check validity of point, making sure both are cleared 0394 if (x == null && y != null) { 0395 // extract min/max info before we whack 0396 updateAxis(s.yaxis, y, y); 0397 y = null; 0398 } 0399 0400 if (y == null && x != null) { 0401 updateAxis(s.xaxis, x, x); 0402 x = null; 0403 } 0404 0405 if (insertSteps && x != null && k > 0 0406 && points[k - ps] != null 0407 && points[k - ps] != x && points[k - ps + 1] != y) { 0408 points[k + 1] = points[k - ps + 1]; 0409 points[k] = x; 0410 0411 // copy the remainding from real point 0412 for (m = 2; m < ps; ++m) 0413 points[k + m] = p[m] == null ? format[m-2].d : p[m]; 0414 k += ps; 0415 } 0416 0417 for (m = 2; m < ps; ++m) 0418 points[k + m] = p == null || p[m] == null ? format[m-2].d : p[m]; 0419 0420 points[k] = x; 0421 points[k + 1] = y; 0422 } 0423 } 0424 0425 for (i = 0; i < series.length; ++i) { 0426 s = series[i]; 0427 0428 executeHooks(hooks.processDatapoints, [ s, s.datapoints]); 0429 } 0430 0431 // second pass: find datamax/datamin for auto-scaling 0432 for (i = 0; i < series.length; ++i) { 0433 s = series[i]; 0434 points = s.datapoints.points, 0435 ps = s.datapoints.pointsize; 0436 0437 var xmin = topSentry, ymin = topSentry, 0438 xmax = bottomSentry, ymax = bottomSentry; 0439 0440 for (j = 0; j < points.length; j += ps) { 0441 x = points[j]; 0442 0443 if (x == null) 0444 continue; 0445 0446 if (x < xmin) 0447 xmin = x; 0448 if (x > xmax) 0449 xmax = x; 0450 0451 y = points[j + 1]; 0452 0453 if (y < ymin) 0454 ymin = y; 0455 if (y > ymax) 0456 ymax = y; 0457 } 0458 0459 if (s.bars.show) { 0460 // make sure we got room for the bar on the dancing floor 0461 var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; 0462 if (s.bars.horizontal) { 0463 ymin += delta; 0464 ymax += delta + s.bars.barWidth; 0465 } 0466 else { 0467 xmin += delta; 0468 xmax += delta + s.bars.barWidth; 0469 } 0470 } 0471 0472 updateAxis(s.xaxis, xmin, xmax); 0473 updateAxis(s.yaxis, ymin, ymax); 0474 } 0475 0476 for (axis in axes) { 0477 if (axes[axis].datamin == topSentry) 0478 axes[axis].datamin = null; 0479 if (axes[axis].datamax == bottomSentry) 0480 axes[axis].datamax = null; 0481 } 0482 } 0483 0484 function constructCanvas() { 0485 function makeCanvas(width, height) { 0486 var c = document.createElement('canvas'); 0487 c.width = width; 0488 c.height = height; 0489 if ($.browser.msie) // excanvas hack 0490 c = window.G_vmlCanvasManager.initElement(c); 0491 return c; 0492 } 0493 0494 canvasWidth = target.width(); 0495 canvasHeight = target.height(); 0496 target.html(""); // clear target 0497 if (target.css("position") == 'static') 0498 target.css("position", "relative"); // for positioning labels and overlay 0499 0500 if (canvasWidth <= 0 || canvasHeight <= 0) 0501 throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; 0502 0503 if ($.browser.msie) // excanvas hack 0504 window.G_vmlCanvasManager.init_(document); // make sure everything is setup 0505 0506 // the canvas 0507 canvas = $(makeCanvas(canvasWidth, canvasHeight)).appendTo(target).get(0); 0508 ctx = canvas.getContext("2d"); 0509 0510 // overlay canvas for interactive features 0511 overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(target).get(0); 0512 octx = overlay.getContext("2d"); 0513 octx.stroke(); 0514 } 0515 0516 function bindEvents() { 0517 // we include the canvas in the event holder too, because IE 7 0518 // sometimes has trouble with the stacking order 0519 eventHolder = $([overlay, canvas]); 0520 0521 // bind events 0522 if (options.selection.mode != null 0523 || options.grid.hoverable) 0524 eventHolder.mousemove(onMouseMove); 0525 0526 if (options.selection.mode != null) 0527 eventHolder.mousedown(onMouseDown); 0528 0529 if (options.grid.clickable) 0530 eventHolder.click(onClick); 0531 0532 executeHooks(hooks.bindEvents, [eventHolder]); 0533 } 0534 0535 function setupGrid() { 0536 function setTransformationHelpers(axis) { 0537 var s, m; 0538 0539 // add transformation helpers 0540 if (axis == axes.xaxis || axis == axes.x2axis) { 0541 // precompute how much the axis is scaling a point 0542 // in canvas space 0543 s = axis.scale = plotWidth / (axis.max - axis.min); 0544 m = axis.min; 0545 0546 // data point to canvas coordinate 0547 axis.p2c = function (p) { return (p - m) * s; }; 0548 // canvas coordinate to data point 0549 axis.c2p = function (c) { return m + c / s; }; 0550 } 0551 else { 0552 s = axis.scale = plotHeight / (axis.max - axis.min); 0553 m = axis.max; 0554 0555 axis.p2c = function (p) { return (m - p) * s; }; 0556 axis.c2p = function (p) { return m - p / s; }; 0557 } 0558 } 0559 0560 var axis; 0561 for (axis in axes) 0562 setRange(axes[axis], options[axis]); 0563 0564 if (options.grid.show) { 0565 for (axis in axes) { 0566 prepareTickGeneration(axes[axis], options[axis]); 0567 setTicks(axes[axis], options[axis]); 0568 } 0569 0570 setGridSpacing(); 0571 } 0572 else { 0573 plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0; 0574 plotWidth = canvasWidth; 0575 plotHeight = canvasHeight; 0576 } 0577 0578 for (axis in axes) 0579 setTransformationHelpers(axes[axis]); 0580 0581 if (options.grid.show) 0582 insertLabels(); 0583 0584 insertLegend(); 0585 } 0586 0587 function setRange(axis, axisOptions) { 0588 var min = +(axisOptions.min != null ? axisOptions.min : axis.datamin), 0589 max = +(axisOptions.max != null ? axisOptions.max : axis.datamax); 0590 0591 if (max - min == 0.0) { 0592 // degenerate case 0593 var widen = max == 0 ? 1 : 0.01; 0594 0595 if (axisOptions.min == null) 0596 min -= widen; 0597 // alway widen max if we couldn't widen min to ensure we 0598 // don't fall into min == max which doesn't work 0599 if (axisOptions.max == null || axisOptions.min != null) 0600 max += widen; 0601 } 0602 else { 0603 // consider autoscaling 0604 var margin = axisOptions.autoscaleMargin; 0605 if (margin != null) { 0606 if (axisOptions.min == null) { 0607 min -= (max - min) * margin; 0608 // make sure we don't go below zero if all values 0609 // are positive 0610 if (min < 0 && axis.datamin != null && axis.datamin >= 0) 0611 min = 0; 0612 } 0613 if (axisOptions.max == null) { 0614 max += (max - min) * margin; 0615 if (max > 0 && axis.datamax != null && axis.datamax <= 0) 0616 max = 0; 0617 } 0618 } 0619 } 0620 axis.min = min; 0621 axis.max = max; 0622 } 0623 0624 function prepareTickGeneration(axis, axisOptions) { 0625 // estimate number of ticks 0626 var noTicks; 0627 if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0) 0628 noTicks = axisOptions.ticks; 0629 else if (axis == axes.xaxis || axis == axes.x2axis) 0630 noTicks = canvasWidth / 100; 0631 else 0632 noTicks = canvasHeight / 60; 0633 0634 var delta = (axis.max - axis.min) / noTicks; 0635 var size, generator, unit, formatter, i, magn, norm; 0636 0637 if (axisOptions.mode == "time") { 0638 // pretty handling of time 0639 0640 // map of app. size of time units in milliseconds 0641 var timeUnitSize = { 0642 "second": 1000, 0643 "minute": 60 * 1000, 0644 "hour": 60 * 60 * 1000, 0645 "day": 24 * 60 * 60 * 1000, 0646 "month": 30 * 24 * 60 * 60 * 1000, 0647 "year": 365.2425 * 24 * 60 * 60 * 1000 0648 }; 0649 0650 0651 // the allowed tick sizes, after 1 year we use 0652 // an integer algorithm 0653 var spec = [ 0654 [1, "second"], [2, "second"], [5, "second"], [10, "second"], 0655 [30, "second"], 0656 [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], 0657 [30, "minute"], 0658 [1, "hour"], [2, "hour"], [4, "hour"], 0659 [8, "hour"], [12, "hour"], 0660 [1, "day"], [2, "day"], [3, "day"], 0661 [0.25, "month"], [0.5, "month"], [1, "month"], 0662 [2, "month"], [3, "month"], [6, "month"], 0663 [1, "year"] 0664 ]; 0665 0666 var minSize = 0; 0667 if (axisOptions.minTickSize != null) { 0668 if (typeof axisOptions.tickSize == "number") 0669 minSize = axisOptions.tickSize; 0670 else 0671 minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]]; 0672 } 0673 0674 for (i = 0; i < spec.length - 1; ++i) 0675 if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] 0676 + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 0677 && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) 0678 break; 0679 size = spec[i][0]; 0680 unit = spec[i][1]; 0681 0682 // special-case the possibility of several years 0683 if (unit == "year") { 0684 magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); 0685 norm = (delta / timeUnitSize.year) / magn; 0686 if (norm < 1.5) 0687 size = 1; 0688 else if (norm < 3) 0689 size = 2; 0690 else if (norm < 7.5) 0691 size = 5; 0692 else 0693 size = 10; 0694 0695 size *= magn; 0696 } 0697 0698 if (axisOptions.tickSize) { 0699 size = axisOptions.tickSize[0]; 0700 unit = axisOptions.tickSize[1]; 0701 } 0702 0703 generator = function(axis) { 0704 var ticks = [], 0705 tickSize = axis.tickSize[0], unit = axis.tickSize[1], 0706 d = new Date(axis.min); 0707 0708 var step = tickSize * timeUnitSize[unit]; 0709 0710 if (unit == "second") 0711 d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); 0712 if (unit == "minute") 0713 d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); 0714 if (unit == "hour") 0715 d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); 0716 if (unit == "month") 0717 d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); 0718 if (unit == "year") 0719 d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); 0720 0721 // reset smaller components 0722 d.setUTCMilliseconds(0); 0723 if (step >= timeUnitSize.minute) 0724 d.setUTCSeconds(0); 0725 if (step >= timeUnitSize.hour) 0726 d.setUTCMinutes(0); 0727 if (step >= timeUnitSize.day) 0728 d.setUTCHours(0); 0729 if (step >= timeUnitSize.day * 4) 0730 d.setUTCDate(1); 0731 if (step >= timeUnitSize.year) 0732 d.setUTCMonth(0); 0733 0734 0735 var carry = 0, v = Number.NaN, prev; 0736 do { 0737 prev = v; 0738 v = d.getTime(); 0739 ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); 0740 if (unit == "month") { 0741 if (tickSize < 1) { 0742 // a bit complicated - we'll divide the month 0743 // up but we need to take care of fractions 0744 // so we don't end up in the middle of a day 0745 d.setUTCDate(1); 0746 var start = d.getTime(); 0747 d.setUTCMonth(d.getUTCMonth() + 1); 0748 var end = d.getTime(); 0749 d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); 0750 carry = d.getUTCHours(); 0751 d.setUTCHours(0); 0752 } 0753 else 0754 d.setUTCMonth(d.getUTCMonth() + tickSize); 0755 } 0756 else if (unit == "year") { 0757 d.setUTCFullYear(d.getUTCFullYear() + tickSize); 0758 } 0759 else 0760 d.setTime(v + step); 0761 } while (v < axis.max && v != prev); 0762 0763 return ticks; 0764 }; 0765 0766 formatter = function (v, axis) { 0767 var d = new Date(v); 0768 0769 // first check global format 0770 if (axisOptions.timeformat != null) 0771 return $.plot.formatDate(d, axisOptions.timeformat, axisOptions.monthNames); 0772 0773 var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; 0774 var span = axis.max - axis.min; 0775 0776 if (t < timeUnitSize.minute) 0777 fmt = "%h:%M:%S"; 0778 else if (t < timeUnitSize.day) { 0779 if (span < 2 * timeUnitSize.day) 0780 fmt = "%h:%M"; 0781 else 0782 fmt = "%b %d %h:%M"; 0783 } 0784 else if (t < timeUnitSize.month) 0785 fmt = "%b %d"; 0786 else if (t < timeUnitSize.year) { 0787 if (span < timeUnitSize.year) 0788 fmt = "%b"; 0789 else 0790 fmt = "%b %y"; 0791 } 0792 else 0793 fmt = "%y"; 0794 0795 return $.plot.formatDate(d, fmt, axisOptions.monthNames); 0796 }; 0797 } 0798 else { 0799 // pretty rounding of base-10 numbers 0800 var maxDec = axisOptions.tickDecimals; 0801 var dec = -Math.floor(Math.log(delta) / Math.LN10); 0802 if (maxDec != null && dec > maxDec) 0803 dec = maxDec; 0804 0805 magn = Math.pow(10, -dec); 0806 norm = delta / magn; // norm is between 1.0 and 10.0 0807 0808 if (norm < 1.5) 0809 size = 1; 0810 else if (norm < 3) { 0811 size = 2; 0812 // special case for 2.5, requires an extra decimal 0813 if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { 0814 size = 2.5; 0815 ++dec; 0816 } 0817 } 0818 else if (norm < 7.5) 0819 size = 5; 0820 else 0821 size = 10; 0822 0823 size *= magn; 0824 0825 if (axisOptions.minTickSize != null && size < axisOptions.minTickSize) 0826 size = axisOptions.minTickSize; 0827 0828 if (axisOptions.tickSize != null) 0829 size = axisOptions.tickSize; 0830 0831 axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec); 0832 0833 generator = function (axis) { 0834 var ticks = []; 0835 0836 // spew out all possible ticks 0837 var start = floorInBase(axis.min, axis.tickSize), 0838 i = 0, v = Number.NaN, prev; 0839 do { 0840 prev = v; 0841 v = start + i * axis.tickSize; 0842 ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); 0843 ++i; 0844 } while (v < axis.max && v != prev); 0845 return ticks; 0846 }; 0847 0848 formatter = function (v, axis) { 0849 return v.toFixed(axis.tickDecimals); 0850 }; 0851 } 0852 0853 axis.tickSize = unit ? [size, unit] : size; 0854 axis.tickGenerator = generator; 0855 if ($.isFunction(axisOptions.tickFormatter)) 0856 axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); }; 0857 else 0858 axis.tickFormatter = formatter; 0859 if (axisOptions.labelWidth != null) 0860 axis.labelWidth = axisOptions.labelWidth; 0861 if (axisOptions.labelHeight != null) 0862 axis.labelHeight = axisOptions.labelHeight; 0863 } 0864 0865 function setTicks(axis, axisOptions) { 0866 axis.ticks = []; 0867 0868 if (!axis.used) 0869 return; 0870 0871 if (axisOptions.ticks == null) 0872 axis.ticks = axis.tickGenerator(axis); 0873 else if (typeof axisOptions.ticks == "number") { 0874 if (axisOptions.ticks > 0) 0875 axis.ticks = axis.tickGenerator(axis); 0876 } 0877 else if (axisOptions.ticks) { 0878 var ticks = axisOptions.ticks; 0879 0880 if ($.isFunction(ticks)) 0881 // generate the ticks 0882 ticks = ticks({ min: axis.min, max: axis.max }); 0883 0884 // clean up the user-supplied ticks, copy them over 0885 var i, v; 0886 for (i = 0; i < ticks.length; ++i) { 0887 var label = null; 0888 var t = ticks[i]; 0889 if (typeof t == "object") { 0890 v = t[0]; 0891 if (t.length > 1) 0892 label = t[1]; 0893 } 0894 else 0895 v = t; 0896 if (label == null) 0897 label = axis.tickFormatter(v, axis); 0898 axis.ticks[i] = { v: v, label: label }; 0899 } 0900 } 0901 0902 if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) { 0903 // snap to ticks 0904 if (axisOptions.min == null) 0905 axis.min = Math.min(axis.min, axis.ticks[0].v); 0906 if (axisOptions.max == null && axis.ticks.length > 1) 0907 axis.max = Math.min(axis.max, axis.ticks[axis.ticks.length - 1].v); 0908 } 0909 } 0910 0911 function setGridSpacing() { 0912 function measureXLabels(axis) { 0913 // to avoid measuring the widths of the labels, we 0914 // construct fixed-size boxes and put the labels inside 0915 // them, we don't need the exact figures and the 0916 // fixed-size box content is easy to center 0917 if (axis.labelWidth == null) 0918 axis.labelWidth = canvasWidth / 6; 0919 0920 // measure x label heights 0921 if (axis.labelHeight == null) { 0922 labels = []; 0923 for (i = 0; i < axis.ticks.length; ++i) { 0924 l = axis.ticks[i].label; 0925 if (l) 0926 labels.push('<div class="tickLabel" style="float:left;width:' + axis.labelWidth + 'px">' + l + '</div>'); 0927 } 0928 0929 axis.labelHeight = 0; 0930 if (labels.length > 0) { 0931 var dummyDiv = $('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">' 0932 + labels.join("") + '<div style="clear:left"></div></div>').appendTo(target); 0933 axis.labelHeight = dummyDiv.height(); 0934 dummyDiv.remove(); 0935 } 0936 } 0937 } 0938 0939 function measureYLabels(axis) { 0940 if (axis.labelWidth == null || axis.labelHeight == null) { 0941 var i, labels = [], l; 0942 // calculate y label dimensions 0943 for (i = 0; i < axis.ticks.length; ++i) { 0944 l = axis.ticks[i].label; 0945 if (l) 0946 labels.push('<div class="tickLabel">' + l + '</div>'); 0947 } 0948 0949 if (labels.length > 0) { 0950 var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">' 0951 + labels.join("") + '</div>').appendTo(target); 0952 if (axis.labelWidth == null) 0953 axis.labelWidth = dummyDiv.width(); 0954 if (axis.labelHeight == null) 0955 axis.labelHeight = dummyDiv.find("div").height(); 0956 dummyDiv.remove(); 0957 } 0958 0959 if (axis.labelWidth == null) 0960 axis.labelWidth = 0; 0961 if (axis.labelHeight == null) 0962 axis.labelHeight = 0; 0963 } 0964 } 0965 0966 // get the most space needed around the grid for things 0967 // that may stick out 0968 var maxOutset = options.grid.borderWidth; 0969 for (i = 0; i < series.length; ++i) 0970 maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); 0971 0972 plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset; 0973 0974 var margin = options.grid.labelMargin + options.grid.borderWidth; 0975 0976 measureXLabels(axes.xaxis); 0977 measureYLabels(axes.yaxis); 0978 measureXLabels(axes.x2axis); 0979 measureYLabels(axes.y2axis); 0980 0981 if (axes.xaxis.labelHeight > 0) 0982 plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin); 0983 if (axes.yaxis.labelWidth > 0) 0984 plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin); 0985 0986 if (axes.x2axis.labelHeight > 0) 0987 plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin); 0988 0989 if (axes.y2axis.labelWidth > 0) 0990 plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin); 0991 0992 plotWidth = canvasWidth - plotOffset.left - plotOffset.right; 0993 plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; 0994 } 0995 0996 function draw() { 0997 if (options.grid.show) 0998 drawGrid(); 0999 1000 for (var i = 0; i < series.length; ++i) 1001 drawSeries(series[i]); 1002 1003 executeHooks(hooks.draw, [ctx]); 1004 } 1005 1006 function extractRange(ranges, coord) { 1007 var firstAxis = coord + "axis", 1008 secondaryAxis = coord + "2axis", 1009 axis, from, to, reverse; 1010 1011 if (ranges[firstAxis]) { 1012 axis = axes[firstAxis]; 1013 from = ranges[firstAxis].from; 1014 to = ranges[firstAxis].to; 1015 } 1016 else if (ranges[secondaryAxis]) { 1017 axis = axes[secondaryAxis]; 1018 from = ranges[secondaryAxis].from; 1019 to = ranges[secondaryAxis].to; 1020 } 1021 else { 1022 // backwards-compat stuff - to be removed in future 1023 axis = axes[firstAxis]; 1024 from = ranges[coord + "1"]; 1025 to = ranges[coord + "2"]; 1026 } 1027 1028 // auto-reverse as an added bonus 1029 if (from != null && to != null && from > to) 1030 return { from: to, to: from, axis: axis }; 1031 1032 return { from: from, to: to, axis: axis }; 1033 } 1034 1035 function drawGrid() { 1036 var i; 1037 1038 ctx.save(); 1039 ctx.clearRect(0, 0, canvasWidth, canvasHeight); 1040 ctx.translate(plotOffset.left, plotOffset.top); 1041 1042 // draw background, if any 1043 if (options.grid.backgroundColor) { 1044 ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); 1045 ctx.fillRect(0, 0, plotWidth, plotHeight); 1046 } 1047 1048 // draw markings 1049 var markings = options.grid.markings; 1050 if (markings) { 1051 if ($.isFunction(markings)) 1052 // xmin etc. are backwards-compatible, to be removed in future 1053 markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis }); 1054 1055 for (i = 0; i < markings.length; ++i) { 1056 var m = markings[i], 1057 xrange = extractRange(m, "x"), 1058 yrange = extractRange(m, "y"); 1059 1060 // fill in missing 1061 if (xrange.from == null) 1062 xrange.from = xrange.axis.min; 1063 if (xrange.to == null) 1064 xrange.to = xrange.axis.max; 1065 if (yrange.from == null) 1066 yrange.from = yrange.axis.min; 1067 if (yrange.to == null) 1068 yrange.to = yrange.axis.max; 1069 1070 // clip 1071 if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || 1072 yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) 1073 continue; 1074 1075 xrange.from = Math.max(xrange.from, xrange.axis.min); 1076 xrange.to = Math.min(xrange.to, xrange.axis.max); 1077 yrange.from = Math.max(yrange.from, yrange.axis.min); 1078 yrange.to = Math.min(yrange.to, yrange.axis.max); 1079 1080 if (xrange.from == xrange.to && yrange.from == yrange.to) 1081 continue; 1082 1083 // then draw 1084 xrange.from = xrange.axis.p2c(xrange.from); 1085 xrange.to = xrange.axis.p2c(xrange.to); 1086 yrange.from = yrange.axis.p2c(yrange.from); 1087 yrange.to = yrange.axis.p2c(yrange.to); 1088 1089 if (xrange.from == xrange.to || yrange.from == yrange.to) { 1090 // draw line 1091 ctx.strokeStyle = m.color || options.grid.markingsColor; 1092 ctx.beginPath(); 1093 ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; 1094 //ctx.moveTo(Math.floor(xrange.from), yrange.from); 1095 //ctx.lineTo(Math.floor(xrange.to), yrange.to); 1096 ctx.moveTo(xrange.from, yrange.from); 1097 ctx.lineTo(xrange.to, yrange.to); 1098 ctx.stroke(); 1099 } 1100 else { 1101 // fill area 1102 ctx.fillStyle = m.color || options.grid.markingsColor; 1103 ctx.fillRect(xrange.from, yrange.to, 1104 xrange.to - xrange.from, 1105 yrange.from - yrange.to); 1106 } 1107 } 1108 } 1109 1110 // draw the inner grid 1111 ctx.lineWidth = 1; 1112 ctx.strokeStyle = options.grid.tickColor; 1113 ctx.beginPath(); 1114 var v, axis = axes.xaxis; 1115 for (i = 0; i < axis.ticks.length; ++i) { 1116 v = axis.ticks[i].v; 1117 if (v <= axis.min || v >= axes.xaxis.max) 1118 continue; // skip those lying on the axes 1119 1120 ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0); 1121 ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight); 1122 } 1123 1124 axis = axes.yaxis; 1125 for (i = 0; i < axis.ticks.length; ++i) { 1126 v = axis.ticks[i].v; 1127 if (v <= axis.min || v >= axis.max) 1128 continue; 1129 1130 ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); 1131 ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); 1132 } 1133 1134 axis = axes.x2axis; 1135 for (i = 0; i < axis.ticks.length; ++i) { 1136 v = axis.ticks[i].v; 1137 if (v <= axis.min || v >= axis.max) 1138 continue; 1139 1140 ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5); 1141 ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5); 1142 } 1143 1144 axis = axes.y2axis; 1145 for (i = 0; i < axis.ticks.length; ++i) { 1146 v = axis.ticks[i].v; 1147 if (v <= axis.min || v >= axis.max) 1148 continue; 1149 1150 ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); 1151 ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); 1152 } 1153 1154 ctx.stroke(); 1155 1156 if (options.grid.borderWidth) { 1157 // draw border 1158 var bw = options.grid.borderWidth; 1159 ctx.lineWidth = bw; 1160 ctx.strokeStyle = options.grid.borderColor; 1161 ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); 1162 } 1163 1164 ctx.restore(); 1165 } 1166 1167 function insertLabels() { 1168 target.find(".tickLabels").remove(); 1169 1170 var html = ['<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">']; 1171 1172 function addLabels(axis, labelGenerator) { 1173 for (var i = 0; i < axis.ticks.length; ++i) { 1174 var tick = axis.ticks[i]; 1175 if (!tick.label || tick.v < axis.min || tick.v > axis.max) 1176 continue; 1177 html.push(labelGenerator(tick, axis)); 1178 } 1179 } 1180 1181 var margin = options.grid.labelMargin + options.grid.borderWidth; 1182 1183 addLabels(axes.xaxis, function (tick, axis) { 1184 return '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>"; 1185 }); 1186 1187 1188 addLabels(axes.yaxis, function (tick, axis) { 1189 return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;right:' + (plotOffset.right + plotWidth + margin) + 'px;width:' + axis.labelWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>"; 1190 }); 1191 1192 addLabels(axes.x2axis, function (tick, axis) { 1193 return '<div style="position:absolute;bottom:' + (plotOffset.bottom + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>"; 1194 }); 1195 1196 addLabels(axes.y2axis, function (tick, axis) { 1197 return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + margin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>"; 1198 }); 1199 1200 html.push('</div>'); 1201 1202 target.append(html.join("")); 1203 } 1204 1205 function drawSeries(series) { 1206 if (series.lines.show) 1207 drawSeriesLines(series); 1208 if (series.bars.show) 1209 drawSeriesBars(series); 1210 if (series.points.show) 1211 drawSeriesPoints(series); 1212 } 1213 1214 function drawSeriesLines(series) { 1215 function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { 1216 var points = datapoints.points, 1217 ps = datapoints.pointsize, 1218 prevx = null, prevy = null; 1219 1220 ctx.beginPath(); 1221 for (var i = ps; i < points.length; i += ps) { 1222 var x1 = points[i - ps], y1 = points[i - ps + 1], 1223 x2 = points[i], y2 = points[i + 1]; 1224 1225 if (x1 == null || x2 == null) 1226 continue; 1227 1228 // clip with ymin 1229 if (y1 <= y2 && y1 < axisy.min) { 1230 if (y2 < axisy.min) 1231 continue; // line segment is outside 1232 // compute new intersection point 1233 x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 1234 y1 = axisy.min; 1235 } 1236 else if (y2 <= y1 && y2 < axisy.min) { 1237 if (y1 < axisy.min) 1238 continue; 1239 x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 1240 y2 = axisy.min; 1241 } 1242 1243 // clip with ymax 1244 if (y1 >= y2 && y1 > axisy.max) { 1245 if (y2 > axisy.max) 1246 continue; 1247 x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 1248 y1 = axisy.max; 1249 } 1250 else if (y2 >= y1 && y2 > axisy.max) { 1251 if (y1 > axisy.max) 1252 continue; 1253 x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 1254 y2 = axisy.max; 1255 } 1256 1257 // clip with xmin 1258 if (x1 <= x2 && x1 < axisx.min) { 1259 if (x2 < axisx.min) 1260 continue; 1261 y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 1262 x1 = axisx.min; 1263 } 1264 else if (x2 <= x1 && x2 < axisx.min) { 1265 if (x1 < axisx.min) 1266 continue; 1267 y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 1268 x2 = axisx.min; 1269 } 1270 1271 // clip with xmax 1272 if (x1 >= x2 && x1 > axisx.max) { 1273 if (x2 > axisx.max) 1274 continue; 1275 y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 1276 x1 = axisx.max; 1277 } 1278 else if (x2 >= x1 && x2 > axisx.max) { 1279 if (x1 > axisx.max) 1280 continue; 1281 y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 1282 x2 = axisx.max; 1283 } 1284 1285 if (x1 != prevx || y1 != prevy) 1286 ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); 1287 1288 prevx = x2; 1289 prevy = y2; 1290 ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); 1291 } 1292 ctx.stroke(); 1293 } 1294 1295 function plotLineArea(datapoints, axisx, axisy) { 1296 var points = datapoints.points, 1297 ps = datapoints.pointsize, 1298 bottom = Math.min(Math.max(0, axisy.min), axisy.max), 1299 top, lastX = 0, areaOpen = false; 1300 1301 for (var i = ps; i < points.length; i += ps) { 1302 var x1 = points[i - ps], y1 = points[i - ps + 1], 1303 x2 = points[i], y2 = points[i + 1]; 1304 1305 if (areaOpen && x1 != null && x2 == null) { 1306 // close area 1307 ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); 1308 ctx.fill(); 1309 areaOpen = false; 1310 continue; 1311 } 1312 1313 if (x1 == null || x2 == null) 1314 continue; 1315 1316 // clip x values 1317 1318 // clip with xmin 1319 if (x1 <= x2 && x1 < axisx.min) { 1320 if (x2 < axisx.min) 1321 continue; 1322 y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 1323 x1 = axisx.min; 1324 } 1325 else if (x2 <= x1 && x2 < axisx.min) { 1326 if (x1 < axisx.min) 1327 continue; 1328 y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 1329 x2 = axisx.min; 1330 } 1331 1332 // clip with xmax 1333 if (x1 >= x2 && x1 > axisx.max) { 1334 if (x2 > axisx.max) 1335 continue; 1336 y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 1337 x1 = axisx.max; 1338 } 1339 else if (x2 >= x1 && x2 > axisx.max) { 1340 if (x1 > axisx.max) 1341 continue; 1342 y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 1343 x2 = axisx.max; 1344 } 1345 1346 if (!areaOpen) { 1347 // open area 1348 ctx.beginPath(); 1349 ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); 1350 areaOpen = true; 1351 } 1352 1353 // now first check the case where both is outside 1354 if (y1 >= axisy.max && y2 >= axisy.max) { 1355 ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); 1356 ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); 1357 lastX = x2; 1358 continue; 1359 } 1360 else if (y1 <= axisy.min && y2 <= axisy.min) { 1361 ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); 1362 ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); 1363 lastX = x2; 1364 continue; 1365 } 1366 1367 // else it's a bit more complicated, there might 1368 // be two rectangles and two triangles we need to fill 1369 // in; to find these keep track of the current x values 1370 var x1old = x1, x2old = x2; 1371 1372 // and clip the y values, without shortcutting 1373 1374 // clip with ymin 1375 if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { 1376 x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 1377 y1 = axisy.min; 1378 } 1379 else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { 1380 x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 1381 y2 = axisy.min; 1382 } 1383 1384 // clip with ymax 1385 if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { 1386 x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 1387 y1 = axisy.max; 1388 } 1389 else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { 1390 x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 1391 y2 = axisy.max; 1392 } 1393 1394 1395 // if the x value was changed we got a rectangle 1396 // to fill 1397 if (x1 != x1old) { 1398 if (y1 <= axisy.min) 1399 top = axisy.min; 1400 else 1401 top = axisy.max; 1402 1403 ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top)); 1404 ctx.lineTo(axisx.p2c(x1), axisy.p2c(top)); 1405 } 1406 1407 // fill the triangles 1408 ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); 1409 ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); 1410 1411 // fill the other rectangle if it's there 1412 if (x2 != x2old) { 1413 if (y2 <= axisy.min) 1414 top = axisy.min; 1415 else 1416 top = axisy.max; 1417 1418 ctx.lineTo(axisx.p2c(x2), axisy.p2c(top)); 1419 ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top)); 1420 } 1421 1422 lastX = Math.max(x2, x2old); 1423 } 1424 1425 if (areaOpen) { 1426 ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); 1427 ctx.fill(); 1428 } 1429 } 1430 1431 ctx.save(); 1432 ctx.translate(plotOffset.left, plotOffset.top); 1433 ctx.lineJoin = "round"; 1434 1435 var lw = series.lines.lineWidth, 1436 sw = series.shadowSize; 1437 // FIXME: consider another form of shadow when filling is turned on 1438 if (lw > 0 && sw > 0) { 1439 // draw shadow as a thick and thin line with transparency 1440 ctx.lineWidth = sw; 1441 ctx.strokeStyle = "rgba(0,0,0,0.1)"; 1442 var xoffset = 1; 1443 plotLine(series.datapoints, xoffset, Math.sqrt((lw/2 + sw/2)*(lw/2 + sw/2) - xoffset*xoffset), series.xaxis, series.yaxis); 1444 ctx.lineWidth = sw/2; 1445 plotLine(series.datapoints, xoffset, Math.sqrt((lw/2 + sw/4)*(lw/2 + sw/4) - xoffset*xoffset), series.xaxis, series.yaxis); 1446 } 1447 1448 ctx.lineWidth = lw; 1449 ctx.strokeStyle = series.color; 1450 var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); 1451 if (fillStyle) { 1452 ctx.fillStyle = fillStyle; 1453 plotLineArea(series.datapoints, series.xaxis, series.yaxis); 1454 } 1455 1456 if (lw > 0) 1457 plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); 1458 ctx.restore(); 1459 } 1460 1461 function drawSeriesPoints(series) { 1462 function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) { 1463 var points = datapoints.points, ps = datapoints.pointsize; 1464 1465 for (var i = 0; i < points.length; i += ps) { 1466 var x = points[i], y = points[i + 1]; 1467 if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) 1468 continue; 1469 1470 ctx.beginPath(); 1471 ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, false); 1472 if (fillStyle) { 1473 ctx.fillStyle = fillStyle; 1474 ctx.fill(); 1475 } 1476 ctx.stroke(); 1477 } 1478 } 1479 1480 ctx.save(); 1481 ctx.translate(plotOffset.left, plotOffset.top); 1482 1483 var lw = series.lines.lineWidth, 1484 sw = series.shadowSize, 1485 radius = series.points.radius; 1486 if (lw > 0 && sw > 0) { 1487 // draw shadow in two steps 1488 var w = sw / 2; 1489 ctx.lineWidth = w; 1490 ctx.strokeStyle = "rgba(0,0,0,0.1)"; 1491 plotPoints(series.datapoints, radius, null, w + w/2, Math.PI, 1492 series.xaxis, series.yaxis); 1493 1494 ctx.strokeStyle = "rgba(0,0,0,0.2)"; 1495 plotPoints(series.datapoints, radius, null, w/2, Math.PI, 1496 series.xaxis, series.yaxis); 1497 } 1498 1499 ctx.lineWidth = lw; 1500 ctx.strokeStyle = series.color; 1501 plotPoints(series.datapoints, radius, 1502 getFillStyle(series.points, series.color), 0, 2 * Math.PI, 1503 series.xaxis, series.yaxis); 1504 ctx.restore(); 1505 } 1506 1507 function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) { 1508 var left, right, bottom, top, 1509 drawLeft, drawRight, drawTop, drawBottom, 1510 tmp; 1511 1512 if (horizontal) { 1513 drawBottom = drawRight = drawTop = true; 1514 drawLeft = false; 1515 left = b; 1516 right = x; 1517 top = y + barLeft; 1518 bottom = y + barRight; 1519 1520 // account for negative bars 1521 if (right < left) { 1522 tmp = right; 1523 right = left; 1524 left = tmp; 1525 drawLeft = true; 1526 drawRight = false; 1527 } 1528 } 1529 else { 1530 drawLeft = drawRight = drawTop = true; 1531 drawBottom = false; 1532 left = x + barLeft; 1533 right = x + barRight; 1534 bottom = b; 1535 top = y; 1536 1537 // account for negative bars 1538 if (top < bottom) { 1539 tmp = top; 1540 top = bottom; 1541 bottom = tmp; 1542 drawBottom = true; 1543 drawTop = false; 1544 } 1545 } 1546 1547 // clip 1548 if (right < axisx.min || left > axisx.max || 1549 top < axisy.min || bottom > axisy.max) 1550 return; 1551 1552 if (left < axisx.min) { 1553 left = axisx.min; 1554 drawLeft = false; 1555 } 1556 1557 if (right > axisx.max) { 1558 right = axisx.max; 1559 drawRight = false; 1560 } 1561 1562 if (bottom < axisy.min) { 1563 bottom = axisy.min; 1564 drawBottom = false; 1565 } 1566 1567 if (top > axisy.max) { 1568 top = axisy.max; 1569 drawTop = false; 1570 } 1571 1572 left = axisx.p2c(left); 1573 bottom = axisy.p2c(bottom); 1574 right = axisx.p2c(right); 1575 top = axisy.p2c(top); 1576 1577 // fill the bar 1578 if (fillStyleCallback) { 1579 c.beginPath(); 1580 c.moveTo(left, bottom); 1581 c.lineTo(left, top); 1582 c.lineTo(right, top); 1583 c.lineTo(right, bottom); 1584 c.fillStyle = fillStyleCallback(bottom, top); 1585 c.fill(); 1586 } 1587 1588 // draw outline 1589 if (drawLeft || drawRight || drawTop || drawBottom) { 1590 c.beginPath(); 1591 1592 // FIXME: inline moveTo is buggy with excanvas 1593 c.moveTo(left, bottom + offset); 1594 if (drawLeft) 1595 c.lineTo(left, top + offset); 1596 else 1597 c.moveTo(left, top + offset); 1598 if (drawTop) 1599 c.lineTo(right, top + offset); 1600 else 1601 c.moveTo(right, top + offset); 1602 if (drawRight) 1603 c.lineTo(right, bottom + offset); 1604 else 1605 c.moveTo(right, bottom + offset); 1606 if (drawBottom) 1607 c.lineTo(left, bottom + offset); 1608 else 1609 c.moveTo(left, bottom + offset); 1610 c.stroke(); 1611 } 1612 } 1613 1614 function drawSeriesBars(series) { 1615 function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { 1616 var points = datapoints.points, ps = datapoints.pointsize; 1617 1618 for (var i = 0; i < points.length; i += ps) { 1619 if (points[i] == null) 1620 continue; 1621 drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal); 1622 } 1623 } 1624 1625 ctx.save(); 1626 ctx.translate(plotOffset.left, plotOffset.top); 1627 1628 // FIXME: figure out a way to add shadows (for instance along the right edge) 1629 ctx.lineWidth = series.bars.lineWidth; 1630 ctx.strokeStyle = series.color; 1631 var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; 1632 var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; 1633 plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); 1634 ctx.restore(); 1635 } 1636 1637 function getFillStyle(filloptions, seriesColor, bottom, top) { 1638 var fill = filloptions.fill; 1639 if (!fill) 1640 return null; 1641 1642 if (filloptions.fillColor) 1643 return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); 1644 1645 var c = parseColor(seriesColor); 1646 c.a = typeof fill == "number" ? fill : 0.4; 1647 c.normalize(); 1648 return c.toString(); 1649 } 1650 1651 function insertLegend() { 1652 target.find(".legend").remove(); 1653 1654 if (!options.legend.show) 1655 return; 1656 1657 var fragments = [], rowStarted = false, 1658 lf = options.legend.labelFormatter, s, label; 1659 for (i = 0; i < series.length; ++i) { 1660 s = series[i]; 1661 label = s.label; 1662 if (!label) 1663 continue; 1664 1665 if (i % options.legend.noColumns == 0) { 1666 if (rowStarted) 1667 fragments.push('</tr>'); 1668 fragments.push('<tr>'); 1669 rowStarted = true; 1670 } 1671 1672 if (lf) 1673 label = lf(label, s); 1674 1675 fragments.push( 1676 '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></div></td>' + 1677 '<td class="legendLabel">' + label + '</td>'); 1678 } 1679 if (rowStarted) 1680 fragments.push('</tr>'); 1681 1682 if (fragments.length == 0) 1683 return; 1684 1685 var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>'; 1686 if (options.legend.container != null) 1687 $(options.legend.container).html(table); 1688 else { 1689 var pos = "", 1690 p = options.legend.position, 1691 m = options.legend.margin; 1692 if (m[0] == null) 1693 m = [m, m]; 1694 if (p.charAt(0) == "n") 1695 pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; 1696 else if (p.charAt(0) == "s") 1697 pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; 1698 if (p.charAt(1) == "e") 1699 pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; 1700 else if (p.charAt(1) == "w") 1701 pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; 1702 var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(target); 1703 if (options.legend.backgroundOpacity != 0.0) { 1704 // put in the transparent background 1705 // separately to avoid blended labels and 1706 // label boxes 1707 var c = options.legend.backgroundColor; 1708 if (c == null) { 1709 var tmp; 1710 if (options.grid.backgroundColor && typeof options.grid.backgroundColor == "string") 1711 tmp = options.grid.backgroundColor; 1712 else 1713 tmp = extractColor(legend); 1714 c = parseColor(tmp).adjust(null, null, null, 1).toString(); 1715 } 1716 var div = legend.children(); 1717 $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity); 1718 } 1719 } 1720 } 1721 1722 1723 // interactive features 1724 1725 var lastMousePos = { pageX: null, pageY: null }, 1726 selection = { 1727 first: { x: -1, y: -1}, second: { x: -1, y: -1}, 1728 show: false, 1729 active: false 1730 }, 1731 highlights = [], 1732 clickIsMouseUp = false, 1733 redrawTimeout = null, 1734 hoverTimeout = null; 1735 1736 // returns the data item the mouse is over, or null if none is found 1737 function findNearbyItem(mouseX, mouseY, seriesFilter) { 1738 var maxDistance = options.grid.mouseActiveRadius, 1739 lowestDistance = maxDistance * maxDistance + 1, 1740 item = null, foundPoint = false, i, j; 1741 1742 for (var i = 0; i < series.length; ++i) { 1743 if (!seriesFilter(series[i])) 1744 continue; 1745 1746 var s = series[i], 1747 axisx = s.xaxis, 1748 axisy = s.yaxis, 1749 points = s.datapoints.points, 1750 ps = s.datapoints.pointsize, 1751 mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster 1752 my = axisy.c2p(mouseY), 1753 maxx = maxDistance / axisx.scale, 1754 maxy = maxDistance / axisy.scale; 1755 1756 if (s.lines.show || s.points.show) { 1757 for (j = 0; j < points.length; j += ps) { 1758 var x = points[j], y = points[j + 1]; 1759 if (x == null) 1760 continue; 1761 1762 // For points and lines, the cursor must be within a 1763 // certain distance to the data point 1764 if (x - mx > maxx || x - mx < -maxx || 1765 y - my > maxy || y - my < -maxy) 1766 continue; 1767 1768 // We have to calculate distances in pixels, not in 1769 // data units, because the scales of the axes may be different 1770 var dx = Math.abs(axisx.p2c(x) - mouseX), 1771 dy = Math.abs(axisy.p2c(y) - mouseY), 1772 dist = dx * dx + dy * dy; // no idea in taking sqrt 1773 if (dist < lowestDistance) { 1774 lowestDistance = dist; 1775 item = [i, j / ps]; 1776 } 1777 } 1778 } 1779 1780 if (s.bars.show && !item) { // no other point can be nearby 1781 var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, 1782 barRight = barLeft + s.bars.barWidth; 1783 1784 for (j = 0; j < points.length; j += ps) { 1785 var x = points[j], y = points[j + 1], b = points[j + 2]; 1786 if (x == null) 1787 continue; 1788 1789 // for a bar graph, the cursor must be inside the bar 1790 if (series[i].bars.horizontal ? 1791 (mx <= Math.max(b, x) && mx >= Math.min(b, x) && 1792 my >= y + barLeft && my <= y + barRight) : 1793 (mx >= x + barLeft && mx <= x + barRight && 1794 my >= Math.min(b, y) && my <= Math.max(b, y))) 1795 item = [i, j / ps]; 1796 } 1797 } 1798 } 1799 1800 if (item) { 1801 i = item[0]; 1802 j = item[1]; 1803 1804 return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), 1805 dataIndex: j, 1806 series: series[i], 1807 seriesIndex: i }; 1808 } 1809 1810 return null; 1811 } 1812 1813 function onMouseMove(e) { 1814 lastMousePos.pageX = e.pageX; 1815 lastMousePos.pageY = e.pageY; 1816 1817 if (options.grid.hoverable) 1818 triggerClickHoverEvent("plothover", lastMousePos, 1819 function (s) { return s["hoverable"] != false; }); 1820 1821 if (selection.active) { 1822 target.trigger("plotselecting", [ getSelection() ]); 1823 1824 updateSelection(lastMousePos); 1825 } 1826 } 1827 1828 function onMouseDown(e) { 1829 if (e.which != 1) // only accept left-click 1830 return; 1831 1832 // cancel out any text selections 1833 document.body.focus(); 1834 1835 // prevent text selection and drag in old-school browsers 1836 if (document.onselectstart !== undefined && workarounds.onselectstart == null) { 1837 workarounds.onselectstart = document.onselectstart; 1838 document.onselectstart = function () { return false; }; 1839 } 1840 if (document.ondrag !== undefined && workarounds.ondrag == null) { 1841 workarounds.ondrag = document.ondrag; 1842 document.ondrag = function () { return false; }; 1843 } 1844 1845 setSelectionPos(selection.first, e); 1846 1847 lastMousePos.pageX = null; 1848 selection.active = true; 1849 $(document).one("mouseup", onSelectionMouseUp); 1850 } 1851 1852 function onClick(e) { 1853 if (clickIsMouseUp) { 1854 clickIsMouseUp = false; 1855 return; 1856 } 1857 1858 triggerClickHoverEvent("plotclick", e, 1859 function (s) { return s["clickable"] != false; }); 1860 } 1861 1862 // trigger click or hover event (they send the same parameters 1863 // so we share their code) 1864 function triggerClickHoverEvent(eventname, event, seriesFilter) { 1865 var offset = eventHolder.offset(), 1866 pos = { pageX: event.pageX, pageY: event.pageY }, 1867 canvasX = event.pageX - offset.left - plotOffset.left, 1868 canvasY = event.pageY - offset.top - plotOffset.top; 1869 1870 if (axes.xaxis.used) 1871 pos.x = axes.xaxis.c2p(canvasX); 1872 if (axes.yaxis.used) 1873 pos.y = axes.yaxis.c2p(canvasY); 1874 if (axes.x2axis.used) 1875 pos.x2 = axes.x2axis.c2p(canvasX); 1876 if (axes.y2axis.used) 1877 pos.y2 = axes.y2axis.c2p(canvasY); 1878 1879 var item = findNearbyItem(canvasX, canvasY, seriesFilter); 1880 1881 if (item) { 1882 // fill in mouse pos for any listeners out there 1883 item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); 1884 item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); 1885 } 1886 1887 if (options.grid.autoHighlight) { 1888 // clear auto-highlights 1889 for (var i = 0; i < highlights.length; ++i) { 1890 var h = highlights[i]; 1891 if (h.auto == eventname && 1892 !(item && h.series == item.series && h.point == item.datapoint)) 1893 unhighlight(h.series, h.point); 1894 } 1895 1896 if (item) 1897 highlight(item.series, item.datapoint, eventname); 1898 } 1899 1900 target.trigger(eventname, [ pos, item ]); 1901 } 1902 1903 function triggerRedrawOverlay() { 1904 if (!redrawTimeout) 1905 redrawTimeout = setTimeout(drawOverlay, 30); 1906 } 1907 1908 function drawOverlay() { 1909 redrawTimeout = null; 1910 1911 // draw highlights 1912 octx.save(); 1913 octx.clearRect(0, 0, canvasWidth, canvasHeight); 1914 octx.translate(plotOffset.left, plotOffset.top); 1915 1916 var i, hi; 1917 for (i = 0; i < highlights.length; ++i) { 1918 hi = highlights[i]; 1919 1920 if (hi.series.bars.show) 1921 drawBarHighlight(hi.series, hi.point); 1922 else 1923 drawPointHighlight(hi.series, hi.point); 1924 } 1925 1926 // draw selection 1927 if (selection.show && selectionIsSane()) { 1928 octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString(); 1929 octx.lineWidth = 1; 1930 ctx.lineJoin = "round"; 1931 octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString(); 1932 1933 var x = Math.min(selection.first.x, selection.second.x), 1934 y = Math.min(selection.first.y, selection.second.y), 1935 w = Math.abs(selection.second.x - selection.first.x), 1936 h = Math.abs(selection.second.y - selection.first.y); 1937 1938 octx.fillRect(x, y, w, h); 1939 octx.strokeRect(x, y, w, h); 1940 } 1941 octx.restore(); 1942 1943 executeHooks(hooks.drawOverlay, [octx]); 1944 } 1945 1946 function highlight(s, point, auto) { 1947 if (typeof s == "number") 1948 s = series[s]; 1949 1950 if (typeof point == "number") 1951 point = s.data[point]; 1952 1953 var i = indexOfHighlight(s, point); 1954 if (i == -1) { 1955 highlights.push({ series: s, point: point, auto: auto }); 1956 1957 triggerRedrawOverlay(); 1958 } 1959 else if (!auto) 1960 highlights[i].auto = false; 1961 } 1962 1963 function unhighlight(s, point) { 1964 if (s == null && point == null) { 1965 highlights = []; 1966 triggerRedrawOverlay(); 1967 } 1968 1969 if (typeof s == "number") 1970 s = series[s]; 1971 1972 if (typeof point == "number") 1973 point = s.data[point]; 1974 1975 var i = indexOfHighlight(s, point); 1976 if (i != -1) { 1977 highlights.splice(i, 1); 1978 1979 triggerRedrawOverlay(); 1980 } 1981 } 1982 1983 function indexOfHighlight(s, p) { 1984 for (var i = 0; i < highlights.length; ++i) { 1985 var h = highlights[i]; 1986 if (h.series == s && h.point[0] == p[0] 1987 && h.point[1] == p[1]) 1988 return i; 1989 } 1990 return -1; 1991 } 1992 1993 function drawPointHighlight(series, point) { 1994 var x = point[0], y = point[1], 1995 axisx = series.xaxis, axisy = series.yaxis; 1996 1997 if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) 1998 return; 1999 2000 var pointRadius = series.points.radius + series.points.lineWidth / 2; 2001 octx.lineWidth = pointRadius; 2002 octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); 2003 var radius = 1.5 * pointRadius; 2004 octx.beginPath(); 2005 octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, false); 2006 octx.stroke(); 2007 } 2008 2009 function drawBarHighlight(series, point) { 2010 octx.lineWidth = series.bars.lineWidth; 2011 octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); 2012 var fillStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); 2013 var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; 2014 drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, 2015 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal); 2016 } 2017 2018 function getSelection() { 2019 if (!selectionIsSane()) 2020 return null; 2021 2022 var x1 = Math.min(selection.first.x, selection.second.x), 2023 x2 = Math.max(selection.first.x, selection.second.x), 2024 y1 = Math.max(selection.first.y, selection.second.y), 2025 y2 = Math.min(selection.first.y, selection.second.y); 2026 2027 var r = {}; 2028 if (axes.xaxis.used) 2029 r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) }; 2030 if (axes.x2axis.used) 2031 r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) }; 2032 if (axes.yaxis.used) 2033 r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) }; 2034 if (axes.y2axis.used) 2035 r.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) }; 2036 return r; 2037 } 2038 2039 function triggerSelectedEvent() { 2040 var r = getSelection(); 2041 2042 target.trigger("plotselected", [ r ]); 2043 2044 // backwards-compat stuff, to be removed in future 2045 if (axes.xaxis.used && axes.yaxis.used) 2046 target.trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); 2047 } 2048 2049 function onSelectionMouseUp(e) { 2050 // revert drag stuff for old-school browsers 2051 if (document.onselectstart !== undefined) 2052 document.onselectstart = workarounds.onselectstart; 2053 if (document.ondrag !== undefined) 2054 document.ondrag = workarounds.ondrag; 2055 2056 // no more draggy-dee-drag 2057 selection.active = false; 2058 updateSelection(e); 2059 2060 if (selectionIsSane()) { 2061 triggerSelectedEvent(); 2062 clickIsMouseUp = true; 2063 } 2064 else { 2065 // this counts as a clear 2066 target.trigger("plotunselected", [ ]); 2067 target.trigger("plotselecting", [ null ]); 2068 } 2069 2070 return false; 2071 } 2072 2073 function setSelectionPos(pos, e) { 2074 var offset = eventHolder.offset(); 2075 pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plotWidth); 2076 pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plotHeight); 2077 2078 if (options.selection.mode == "y") { 2079 if (pos == selection.first) 2080 pos.x = 0; 2081 else 2082 pos.x = plotWidth; 2083 } 2084 2085 if (options.selection.mode == "x") { 2086 if (pos == selection.first) 2087 pos.y = 0; 2088 else 2089 pos.y = plotHeight; 2090 } 2091 } 2092 2093 function updateSelection(pos) { 2094 if (pos.pageX == null) 2095 return; 2096 2097 setSelectionPos(selection.second, pos); 2098 if (selectionIsSane()) { 2099 selection.show = true; 2100 triggerRedrawOverlay(); 2101 } 2102 else 2103 clearSelection(true); 2104 } 2105 2106 function clearSelection(preventEvent) { 2107 if (selection.show) { 2108 selection.show = false; 2109 triggerRedrawOverlay(); 2110 if (!preventEvent) 2111 target.trigger("plotunselected", [ ]); 2112 } 2113 } 2114 2115 function setSelection(ranges, preventEvent) { 2116 var range; 2117 2118 if (options.selection.mode == "y") { 2119 selection.first.x = 0; 2120 selection.second.x = plotWidth; 2121 } 2122 else { 2123 range = extractRange(ranges, "x"); 2124 2125 selection.first.x = range.axis.p2c(range.from); 2126 selection.second.x = range.axis.p2c(range.to); 2127 } 2128 2129 if (options.selection.mode == "x") { 2130 selection.first.y = 0; 2131 selection.second.y = plotHeight; 2132 } 2133 else { 2134 range = extractRange(ranges, "y"); 2135 2136 selection.first.y = range.axis.p2c(range.from); 2137 selection.second.y = range.axis.p2c(range.to); 2138 } 2139 2140 selection.show = true; 2141 triggerRedrawOverlay(); 2142 if (!preventEvent) 2143 triggerSelectedEvent(); 2144 } 2145 2146 function selectionIsSane() { 2147 var minSize = 5; 2148 return Math.abs(selection.second.x - selection.first.x) >= minSize && 2149 Math.abs(selection.second.y - selection.first.y) >= minSize; 2150 } 2151 2152 function getColorOrGradient(spec, bottom, top, defaultColor) { 2153 if (typeof spec == "string") 2154 return spec; 2155 else { 2156 // assume this is a gradient spec; IE currently only 2157 // supports a simple vertical gradient properly, so that's 2158 // what we support too 2159 var gradient = ctx.createLinearGradient(0, top, 0, bottom); 2160 2161 for (var i = 0, l = spec.colors.length; i < l; ++i) { 2162 var c = spec.colors[i]; 2163 gradient.addColorStop(i / (l - 1), typeof c == "string" ? c : parseColor(defaultColor).scale(c.brightness, c.brightness, c.brightness, c.opacity)); 2164 } 2165 2166 return gradient; 2167 } 2168 } 2169 } 2170 2171 $.plot = function(target, data, options) { 2172 var plot = new Plot($(target), data, options, $.plot.plugins); 2173 /*var t0 = new Date(); 2174 var t1 = new Date(); 2175 var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime()) 2176 if (window.console) 2177 console.log(tstr); 2178 else 2179 alert(tstr);*/ 2180 return plot; 2181 }; 2182 2183 $.plot.plugins = []; 2184 2185 // returns a string with the date d formatted according to fmt 2186 $.plot.formatDate = function(d, fmt, monthNames) { 2187 var leftPad = function(n) { 2188 n = "" + n; 2189 return n.length == 1 ? "0" + n : n; 2190 }; 2191 2192 var r = []; 2193 var escape = false; 2194 if (monthNames == null) 2195 monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; 2196 for (var i = 0; i < fmt.length; ++i) { 2197 var c = fmt.charAt(i); 2198 2199 if (escape) { 2200 switch (c) { 2201 case 'h': c = "" + d.getUTCHours(); break; 2202 case 'H': c = leftPad(d.getUTCHours()); break; 2203 case 'M': c = leftPad(d.getUTCMinutes()); break; 2204 case 'S': c = leftPad(d.getUTCSeconds()); break; 2205 case 'd': c = "" + d.getUTCDate(); break; 2206 case 'm': c = "" + (d.getUTCMonth() + 1); break; 2207 case 'y': c = "" + d.getUTCFullYear(); break; 2208 case 'b': c = "" + monthNames[d.getUTCMonth()]; break; 2209 } 2210 r.push(c); 2211 escape = false; 2212 } 2213 else { 2214 if (c == "%") 2215 escape = true; 2216 else 2217 r.push(c); 2218 } 2219 } 2220 return r.join(""); 2221 }; 2222 2223 // round to nearby lower multiple of base 2224 function floorInBase(n, base) { 2225 return base * Math.floor(n / base); 2226 } 2227 2228 function clamp(min, value, max) { 2229 if (value < min) 2230 return min; 2231 else if (value > max) 2232 return max; 2233 else 2234 return value; 2235 } 2236 2237 // color helpers, inspiration from the jquery color animation 2238 // plugin by John Resig 2239 function Color (r, g, b, a) { 2240 2241 var rgba = ['r','g','b','a']; 2242 var x = 4; //rgba.length 2243 2244 while (-1<--x) { 2245 this[rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); 2246 } 2247 2248 this.toString = function() { 2249 if (this.a >= 1.0) { 2250 return "rgb("+[this.r,this.g,this.b].join(",")+")"; 2251 } else { 2252 return "rgba("+[this.r,this.g,this.b,this.a].join(",")+")"; 2253 } 2254 }; 2255 2256 this.scale = function(rf, gf, bf, af) { 2257 x = 4; //rgba.length 2258 while (-1<--x) { 2259 if (arguments[x] != null) 2260 this[rgba[x]] *= arguments[x]; 2261 } 2262 return this.normalize(); 2263 }; 2264 2265 this.adjust = function(rd, gd, bd, ad) { 2266 x = 4; //rgba.length 2267 while (-1<--x) { 2268 if (arguments[x] != null) 2269 this[rgba[x]] += arguments[x]; 2270 } 2271 return this.normalize(); 2272 }; 2273 2274 this.clone = function() { 2275 return new Color(this.r, this.b, this.g, this.a); 2276 }; 2277 2278 var limit = function(val,minVal,maxVal) { 2279 return Math.max(Math.min(val, maxVal), minVal); 2280 }; 2281 2282 this.normalize = function() { 2283 this.r = clamp(0, parseInt(this.r), 255); 2284 this.g = clamp(0, parseInt(this.g), 255); 2285 this.b = clamp(0, parseInt(this.b), 255); 2286 this.a = clamp(0, this.a, 1); 2287 return this; 2288 }; 2289 2290 this.normalize(); 2291 } 2292 2293 var lookupColors = { 2294 aqua:[0,255,255], 2295 azure:[240,255,255], 2296 beige:[245,245,220], 2297 black:[0,0,0], 2298 blue:[0,0,255], 2299 brown:[165,42,42], 2300 cyan:[0,255,255], 2301 darkblue:[0,0,139], 2302 darkcyan:[0,139,139], 2303 darkgrey:[169,169,169], 2304 darkgreen:[0,100,0], 2305 darkkhaki:[189,183,107], 2306 darkmagenta:[139,0,139], 2307 darkolivegreen:[85,107,47], 2308 darkorange:[255,140,0], 2309 darkorchid:[153,50,204], 2310 darkred:[139,0,0], 2311 darksalmon:[233,150,122], 2312 darkviolet:[148,0,211], 2313 fuchsia:[255,0,255], 2314 gold:[255,215,0], 2315 green:[0,128,0], 2316 indigo:[75,0,130], 2317 khaki:[240,230,140], 2318 lightblue:[173,216,230], 2319 lightcyan:[224,255,255], 2320 lightgreen:[144,238,144], 2321 lightgrey:[211,211,211], 2322 lightpink:[255,182,193], 2323 lightyellow:[255,255,224], 2324 lime:[0,255,0], 2325 magenta:[255,0,255], 2326 maroon:[128,0,0], 2327 navy:[0,0,128], 2328 olive:[128,128,0], 2329 orange:[255,165,0], 2330 pink:[255,192,203], 2331 purple:[128,0,128], 2332 violet:[128,0,128], 2333 red:[255,0,0], 2334 silver:[192,192,192], 2335 white:[255,255,255], 2336 yellow:[255,255,0] 2337 }; 2338 2339 function extractColor(element) { 2340 var color, elem = element; 2341 do { 2342 color = elem.css("background-color").toLowerCase(); 2343 // keep going until we find an element that has color, or 2344 // we hit the body 2345 if (color != '' && color != 'transparent') 2346 break; 2347 elem = elem.parent(); 2348 } while (!$.nodeName(elem.get(0), "body")); 2349 2350 // catch Safari's way of signalling transparent 2351 if (color == "rgba(0, 0, 0, 0)") 2352 return "transparent"; 2353 2354 return color; 2355 } 2356 2357 // parse string, returns Color 2358 function parseColor(str) { 2359 var result; 2360 2361 // Look for rgb(num,num,num) 2362 if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)) 2363 return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10)); 2364 2365 // Look for rgba(num,num,num,num) 2366 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)) 2367 return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4])); 2368 2369 // Look for rgb(num%,num%,num%) 2370 if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)) 2371 return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55); 2372 2373 // Look for rgba(num%,num%,num%,num) 2374 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)) 2375 return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4])); 2376 2377 // Look for #a0b1c2 2378 if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)) 2379 return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)); 2380 2381 // Look for #fff 2382 if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)) 2383 return new Color(parseInt(result[1]+result[1], 16), parseInt(result[2]+result[2], 16), parseInt(result[3]+result[3], 16)); 2384 2385 // Otherwise, we're most likely dealing with a named color 2386 var name = $.trim(str).toLowerCase(); 2387 if (name == "transparent") 2388 return new Color(255, 255, 255, 0); 2389 else { 2390 result = lookupColors[name]; 2391 return new Color(result[0], result[1], result[2]); 2392 } 2393 } 2394 2395 })(jQuery);