uawdijnntqw1x1x1
IP : 216.73.217.60
Hostname : webm005.cluster127.gra.hosting.ovh.net
Kernel : Linux webm005.cluster127.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64
Disable Function : _dyuweyrj4,_dyuweyrj4r,dl
OS : Linux
PATH:
/
home
/
histoio
/
www
/
f41a3
/
..
/
plugins-dist
/
..
/
prive
/
..
/
35802
/
javascript.tar
/
/
jquery.flot.time.js000060400000026770152526040300010326 0ustar00/* Pretty handling of time axes. Copyright (c) 2007-2014 IOLA and Ole Laursen. Licensed under the MIT license. Set axis.mode to "time" to enable. See the section "Time series data" in API.txt for details. */ (function($) { var options = { xaxis: { timezone: null, // "browser" for local to the client or timezone for timezone-js timeformat: null, // format string to use twelveHourClock: false, // 12 or 24 time in time mode monthNames: null // list of names of months } }; // round to nearby lower multiple of base function floorInBase(n, base) { return base * Math.floor(n / base); } // Returns a string with the date d formatted according to fmt. // A subset of the Open Group's strftime format is supported. function formatDate(d, fmt, monthNames, dayNames) { if (typeof d.strftime == "function") { return d.strftime(fmt); } var leftPad = function(n, pad) { n = "" + n; pad = "" + (pad == null ? "0" : pad); return n.length == 1 ? pad + n : n; }; var r = []; var escape = false; var hours = d.getHours(); var isAM = hours < 12; if (monthNames == null) { monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; } if (dayNames == null) { dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; } var hours12; if (hours > 12) { hours12 = hours - 12; } else if (hours == 0) { hours12 = 12; } else { hours12 = hours; } for (var i = 0; i < fmt.length; ++i) { var c = fmt.charAt(i); if (escape) { switch (c) { case 'a': c = "" + dayNames[d.getDay()]; break; case 'b': c = "" + monthNames[d.getMonth()]; break; case 'd': c = leftPad(d.getDate()); break; case 'e': c = leftPad(d.getDate(), " "); break; case 'h': // For back-compat with 0.7; remove in 1.0 case 'H': c = leftPad(hours); break; case 'I': c = leftPad(hours12); break; case 'l': c = leftPad(hours12, " "); break; case 'm': c = leftPad(d.getMonth() + 1); break; case 'M': c = leftPad(d.getMinutes()); break; // quarters not in Open Group's strftime specification case 'q': c = "" + (Math.floor(d.getMonth() / 3) + 1); break; case 'S': c = leftPad(d.getSeconds()); break; case 'y': c = leftPad(d.getFullYear() % 100); break; case 'Y': c = "" + d.getFullYear(); break; case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; case 'w': c = "" + d.getDay(); break; } r.push(c); escape = false; } else { if (c == "%") { escape = true; } else { r.push(c); } } } return r.join(""); } // To have a consistent view of time-based data independent of which time // zone the client happens to be in we need a date-like object independent // of time zones. This is done through a wrapper that only calls the UTC // versions of the accessor methods. function makeUtcWrapper(d) { function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) { sourceObj[sourceMethod] = function() { return targetObj[targetMethod].apply(targetObj, arguments); }; }; var utc = { date: d }; // support strftime, if found if (d.strftime != undefined) { addProxyMethod(utc, "strftime", d, "strftime"); } addProxyMethod(utc, "getTime", d, "getTime"); addProxyMethod(utc, "setTime", d, "setTime"); var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"]; for (var p = 0; p < props.length; p++) { addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); } return utc; }; // select time zone strategy. This returns a date-like object tied to the // desired timezone function dateGenerator(ts, opts) { if (opts.timezone == "browser") { return new Date(ts); } else if (!opts.timezone || opts.timezone == "utc") { return makeUtcWrapper(new Date(ts)); } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { var d = new timezoneJS.Date(); // timezone-js is fickle, so be sure to set the time zone before // setting the time. d.setTimezone(opts.timezone); d.setTime(ts); return d; } else { return makeUtcWrapper(new Date(ts)); } } // map of app. size of time units in milliseconds var timeUnitSize = { "second": 1000, "minute": 60 * 1000, "hour": 60 * 60 * 1000, "day": 24 * 60 * 60 * 1000, "month": 30 * 24 * 60 * 60 * 1000, "quarter": 3 * 30 * 24 * 60 * 60 * 1000, "year": 365.2425 * 24 * 60 * 60 * 1000 }; // the allowed tick sizes, after 1 year we use // an integer algorithm var baseSpec = [ [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], [30, "minute"], [1, "hour"], [2, "hour"], [4, "hour"], [8, "hour"], [12, "hour"], [1, "day"], [2, "day"], [3, "day"], [0.25, "month"], [0.5, "month"], [1, "month"], [2, "month"] ]; // we don't know which variant(s) we'll need yet, but generating both is // cheap var specMonths = baseSpec.concat([[3, "month"], [6, "month"], [1, "year"]]); var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], [1, "year"]]); function init(plot) { plot.hooks.processOptions.push(function (plot, options) { $.each(plot.getAxes(), function(axisName, axis) { var opts = axis.options; if (opts.mode == "time") { axis.tickGenerator = function(axis) { var ticks = []; var d = dateGenerator(axis.min, opts); var minSize = 0; // make quarter use a possibility if quarters are // mentioned in either of these options var spec = (opts.tickSize && opts.tickSize[1] === "quarter") || (opts.minTickSize && opts.minTickSize[1] === "quarter") ? specQuarters : specMonths; if (opts.minTickSize != null) { if (typeof opts.tickSize == "number") { minSize = opts.tickSize; } else { minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; } } for (var i = 0; i < spec.length - 1; ++i) { if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { break; } } var size = spec[i][0]; var unit = spec[i][1]; // special-case the possibility of several years if (unit == "year") { // if given a minTickSize in years, just use it, // ensuring that it's an integer if (opts.minTickSize != null && opts.minTickSize[1] == "year") { size = Math.floor(opts.minTickSize[0]); } else { var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); var norm = (axis.delta / timeUnitSize.year) / magn; if (norm < 1.5) { size = 1; } else if (norm < 3) { size = 2; } else if (norm < 7.5) { size = 5; } else { size = 10; } size *= magn; } // minimum size for years is 1 if (size < 1) { size = 1; } } axis.tickSize = opts.tickSize || [size, unit]; var tickSize = axis.tickSize[0]; unit = axis.tickSize[1]; var step = tickSize * timeUnitSize[unit]; if (unit == "second") { d.setSeconds(floorInBase(d.getSeconds(), tickSize)); } else if (unit == "minute") { d.setMinutes(floorInBase(d.getMinutes(), tickSize)); } else if (unit == "hour") { d.setHours(floorInBase(d.getHours(), tickSize)); } else if (unit == "month") { d.setMonth(floorInBase(d.getMonth(), tickSize)); } else if (unit == "quarter") { d.setMonth(3 * floorInBase(d.getMonth() / 3, tickSize)); } else if (unit == "year") { d.setFullYear(floorInBase(d.getFullYear(), tickSize)); } // reset smaller components d.setMilliseconds(0); if (step >= timeUnitSize.minute) { d.setSeconds(0); } if (step >= timeUnitSize.hour) { d.setMinutes(0); } if (step >= timeUnitSize.day) { d.setHours(0); } if (step >= timeUnitSize.day * 4) { d.setDate(1); } if (step >= timeUnitSize.month * 2) { d.setMonth(floorInBase(d.getMonth(), 3)); } if (step >= timeUnitSize.quarter * 2) { d.setMonth(floorInBase(d.getMonth(), 6)); } if (step >= timeUnitSize.year) { d.setMonth(0); } var carry = 0; var v = Number.NaN; var prev; do { prev = v; v = d.getTime(); ticks.push(v); if (unit == "month" || unit == "quarter") { if (tickSize < 1) { // a bit complicated - we'll divide the // month/quarter up but we need to take // care of fractions so we don't end up in // the middle of a day d.setDate(1); var start = d.getTime(); d.setMonth(d.getMonth() + (unit == "quarter" ? 3 : 1)); var end = d.getTime(); d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); carry = d.getHours(); d.setHours(0); } else { d.setMonth(d.getMonth() + tickSize * (unit == "quarter" ? 3 : 1)); } } else if (unit == "year") { d.setFullYear(d.getFullYear() + tickSize); } else { d.setTime(v + step); } } while (v < axis.max && v != prev); return ticks; }; axis.tickFormatter = function (v, axis) { var d = dateGenerator(v, axis.options); // first check global format if (opts.timeformat != null) { return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); } // possibly use quarters if quarters are mentioned in // any of these places var useQuarters = (axis.options.tickSize && axis.options.tickSize[1] == "quarter") || (axis.options.minTickSize && axis.options.minTickSize[1] == "quarter"); var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; var span = axis.max - axis.min; var suffix = (opts.twelveHourClock) ? " %p" : ""; var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; var fmt; if (t < timeUnitSize.minute) { fmt = hourCode + ":%M:%S" + suffix; } else if (t < timeUnitSize.day) { if (span < 2 * timeUnitSize.day) { fmt = hourCode + ":%M" + suffix; } else { fmt = "%b %d " + hourCode + ":%M" + suffix; } } else if (t < timeUnitSize.month) { fmt = "%b %d"; } else if ((useQuarters && t < timeUnitSize.quarter) || (!useQuarters && t < timeUnitSize.year)) { if (span < timeUnitSize.year) { fmt = "%b"; } else { fmt = "%b %Y"; } } else if (useQuarters && t < timeUnitSize.year) { if (span < timeUnitSize.year) { fmt = "Q%q"; } else { fmt = "Q%q %Y"; } } else { fmt = "%Y"; } var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); return rt; }; } }); }); } $.plot.plugins.push({ init: init, options: options, name: 'time', version: '1.0' }); // Time-axis support used to be in Flot core, which exposed the // formatDate function on the plot object. Various plugins depend // on the function, so we need to re-expose it here. $.plot.formatDate = formatDate; $.plot.dateGenerator = dateGenerator; })(jQuery); excanvas.js000060400000121727152526040300006715 0ustar00// Copyright 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Known Issues: // // * Patterns only support repeat. // * Radial gradient are not implemented. The VML version of these look very // different from the canvas one. // * Clipping paths are not implemented. // * Coordsize. The width and height attribute have higher priority than the // width and height style values which isn't correct. // * Painting mode isn't implemented. // * Canvas width/height should is using content-box by default. IE in // Quirks mode will draw the canvas using border-box. Either change your // doctype to HTML5 // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) // or use Box Sizing Behavior from WebFX // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) // * Non uniform scaling does not correctly scale strokes. // * Filling very large shapes (above 5000 points) is buggy. // * Optimize. There is always room for speed improvements. // Only add this code if we do not already have a canvas implementation if (!document.createElement('canvas').getContext) { (function() { // alias some functions to make (compiled) code shorter var m = Math; var mr = m.round; var ms = m.sin; var mc = m.cos; var abs = m.abs; var sqrt = m.sqrt; // this is used for sub pixel precision var Z = 10; var Z2 = Z / 2; var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1]; /** * This funtion is assigned to the <canvas> elements as element.getContext(). * @this {HTMLElement} * @return {CanvasRenderingContext2D_} */ function getContext() { return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this)); } var slice = Array.prototype.slice; /** * Binds a function to an object. The returned function will always use the * passed in {@code obj} as {@code this}. * * Example: * * g = bind(f, obj, a, b) * g(c, d) // will do f.call(obj, a, b, c, d) * * @param {Function} f The function to bind the object to * @param {Object} obj The object that should act as this when the function * is called * @param {*} var_args Rest arguments that will be used as the initial * arguments when the function is called * @return {Function} A new function that has bound this */ function bind(f, obj, var_args) { var a = slice.call(arguments, 2); return function() { return f.apply(obj, a.concat(slice.call(arguments))); }; } function encodeHtmlAttribute(s) { return String(s).replace(/&/g, '&').replace(/"/g, '"'); } function addNamespace(doc, prefix, urn) { if (!doc.namespaces[prefix]) { doc.namespaces.add(prefix, urn, '#default#VML'); } } function addNamespacesAndStylesheet(doc) { addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml'); addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office'); // Setup default CSS. Only add one style sheet per document if (!doc.styleSheets['ex_canvas_']) { var ss = doc.createStyleSheet(); ss.owningElement.id = 'ex_canvas_'; ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera 'text-align:left;width:300px;height:150px}'; } } // Add namespaces and stylesheet at startup. addNamespacesAndStylesheet(document); var G_vmlCanvasManager_ = { init: function(opt_doc) { var doc = opt_doc || document; // Create a dummy element so that IE will allow canvas elements to be // recognized. doc.createElement('canvas'); doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); }, init_: function(doc) { // find all canvas elements var els = doc.getElementsByTagName('canvas'); for (var i = 0; i < els.length; i++) { this.initElement(els[i]); } }, /** * Public initializes a canvas element so that it can be used as canvas * element from now on. This is called automatically before the page is * loaded but if you are creating elements using createElement you need to * make sure this is called on the element. * @param {HTMLElement} el The canvas element to initialize. * @return {HTMLElement} the element that was created. */ initElement: function(el) { if (!el.getContext) { el.getContext = getContext; // Add namespaces and stylesheet to document of the element. addNamespacesAndStylesheet(el.ownerDocument); // Remove fallback content. There is no way to hide text nodes so we // just remove all childNodes. We could hide all elements and remove // text nodes but who really cares about the fallback content. el.innerHTML = ''; // do not use inline function because that will leak memory el.attachEvent('onpropertychange', onPropertyChange); el.attachEvent('onresize', onResize); var attrs = el.attributes; if (attrs.width && attrs.width.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setWidth_(attrs.width.nodeValue); el.style.width = attrs.width.nodeValue + 'px'; } else { el.width = el.clientWidth; } if (attrs.height && attrs.height.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setHeight_(attrs.height.nodeValue); el.style.height = attrs.height.nodeValue + 'px'; } else { el.height = el.clientHeight; } //el.getContext().setCoordsize_() } return el; } }; function onPropertyChange(e) { var el = e.srcElement; switch (e.propertyName) { case 'width': el.getContext().clearRect(); el.style.width = el.attributes.width.nodeValue + 'px'; // In IE8 this does not trigger onresize. el.firstChild.style.width = el.clientWidth + 'px'; break; case 'height': el.getContext().clearRect(); el.style.height = el.attributes.height.nodeValue + 'px'; el.firstChild.style.height = el.clientHeight + 'px'; break; } } function onResize(e) { var el = e.srcElement; if (el.firstChild) { el.firstChild.style.width = el.clientWidth + 'px'; el.firstChild.style.height = el.clientHeight + 'px'; } } G_vmlCanvasManager_.init(); // precompute "00" to "FF" var decToHex = []; for (var i = 0; i < 16; i++) { for (var j = 0; j < 16; j++) { decToHex[i * 16 + j] = i.toString(16) + j.toString(16); } } function createMatrixIdentity() { return [ [1, 0, 0], [0, 1, 0], [0, 0, 1] ]; } function matrixMultiply(m1, m2) { var result = createMatrixIdentity(); for (var x = 0; x < 3; x++) { for (var y = 0; y < 3; y++) { var sum = 0; for (var z = 0; z < 3; z++) { sum += m1[x][z] * m2[z][y]; } result[x][y] = sum; } } return result; } function copyState(o1, o2) { o2.fillStyle = o1.fillStyle; o2.lineCap = o1.lineCap; o2.lineJoin = o1.lineJoin; o2.lineWidth = o1.lineWidth; o2.miterLimit = o1.miterLimit; o2.shadowBlur = o1.shadowBlur; o2.shadowColor = o1.shadowColor; o2.shadowOffsetX = o1.shadowOffsetX; o2.shadowOffsetY = o1.shadowOffsetY; o2.strokeStyle = o1.strokeStyle; o2.globalAlpha = o1.globalAlpha; o2.font = o1.font; o2.textAlign = o1.textAlign; o2.textBaseline = o1.textBaseline; o2.arcScaleX_ = o1.arcScaleX_; o2.arcScaleY_ = o1.arcScaleY_; o2.lineScale_ = o1.lineScale_; } var colorData = { aliceblue: '#F0F8FF', antiquewhite: '#FAEBD7', aquamarine: '#7FFFD4', azure: '#F0FFFF', beige: '#F5F5DC', bisque: '#FFE4C4', black: '#000000', blanchedalmond: '#FFEBCD', blueviolet: '#8A2BE2', brown: '#A52A2A', burlywood: '#DEB887', cadetblue: '#5F9EA0', chartreuse: '#7FFF00', chocolate: '#D2691E', coral: '#FF7F50', cornflowerblue: '#6495ED', cornsilk: '#FFF8DC', crimson: '#DC143C', cyan: '#00FFFF', darkblue: '#00008B', darkcyan: '#008B8B', darkgoldenrod: '#B8860B', darkgray: '#A9A9A9', darkgreen: '#006400', darkgrey: '#A9A9A9', darkkhaki: '#BDB76B', darkmagenta: '#8B008B', darkolivegreen: '#556B2F', darkorange: '#FF8C00', darkorchid: '#9932CC', darkred: '#8B0000', darksalmon: '#E9967A', darkseagreen: '#8FBC8F', darkslateblue: '#483D8B', darkslategray: '#2F4F4F', darkslategrey: '#2F4F4F', darkturquoise: '#00CED1', darkviolet: '#9400D3', deeppink: '#FF1493', deepskyblue: '#00BFFF', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1E90FF', firebrick: '#B22222', floralwhite: '#FFFAF0', forestgreen: '#228B22', gainsboro: '#DCDCDC', ghostwhite: '#F8F8FF', gold: '#FFD700', goldenrod: '#DAA520', grey: '#808080', greenyellow: '#ADFF2F', honeydew: '#F0FFF0', hotpink: '#FF69B4', indianred: '#CD5C5C', indigo: '#4B0082', ivory: '#FFFFF0', khaki: '#F0E68C', lavender: '#E6E6FA', lavenderblush: '#FFF0F5', lawngreen: '#7CFC00', lemonchiffon: '#FFFACD', lightblue: '#ADD8E6', lightcoral: '#F08080', lightcyan: '#E0FFFF', lightgoldenrodyellow: '#FAFAD2', lightgreen: '#90EE90', lightgrey: '#D3D3D3', lightpink: '#FFB6C1', lightsalmon: '#FFA07A', lightseagreen: '#20B2AA', lightskyblue: '#87CEFA', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#B0C4DE', lightyellow: '#FFFFE0', limegreen: '#32CD32', linen: '#FAF0E6', magenta: '#FF00FF', mediumaquamarine: '#66CDAA', mediumblue: '#0000CD', mediumorchid: '#BA55D3', mediumpurple: '#9370DB', mediumseagreen: '#3CB371', mediumslateblue: '#7B68EE', mediumspringgreen: '#00FA9A', mediumturquoise: '#48D1CC', mediumvioletred: '#C71585', midnightblue: '#191970', mintcream: '#F5FFFA', mistyrose: '#FFE4E1', moccasin: '#FFE4B5', navajowhite: '#FFDEAD', oldlace: '#FDF5E6', olivedrab: '#6B8E23', orange: '#FFA500', orangered: '#FF4500', orchid: '#DA70D6', palegoldenrod: '#EEE8AA', palegreen: '#98FB98', paleturquoise: '#AFEEEE', palevioletred: '#DB7093', papayawhip: '#FFEFD5', peachpuff: '#FFDAB9', peru: '#CD853F', pink: '#FFC0CB', plum: '#DDA0DD', powderblue: '#B0E0E6', rosybrown: '#BC8F8F', royalblue: '#4169E1', saddlebrown: '#8B4513', salmon: '#FA8072', sandybrown: '#F4A460', seagreen: '#2E8B57', seashell: '#FFF5EE', sienna: '#A0522D', skyblue: '#87CEEB', slateblue: '#6A5ACD', slategray: '#708090', slategrey: '#708090', snow: '#FFFAFA', springgreen: '#00FF7F', steelblue: '#4682B4', tan: '#D2B48C', thistle: '#D8BFD8', tomato: '#FF6347', turquoise: '#40E0D0', violet: '#EE82EE', wheat: '#F5DEB3', whitesmoke: '#F5F5F5', yellowgreen: '#9ACD32' }; function getRgbHslContent(styleString) { var start = styleString.indexOf('(', 3); var end = styleString.indexOf(')', start + 1); var parts = styleString.substring(start + 1, end).split(','); // add alpha if needed if (parts.length != 4 || styleString.charAt(3) != 'a') { parts[3] = 1; } return parts; } function percent(s) { return parseFloat(s) / 100; } function clamp(v, min, max) { return Math.min(max, Math.max(min, v)); } function hslToRgb(parts){ var r, g, b, h, s, l; h = parseFloat(parts[0]) / 360 % 360; if (h < 0) h++; s = clamp(percent(parts[1]), 0, 1); l = clamp(percent(parts[2]), 0, 1); if (s == 0) { r = g = b = l; // achromatic } else { var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hueToRgb(p, q, h + 1 / 3); g = hueToRgb(p, q, h); b = hueToRgb(p, q, h - 1 / 3); } return '#' + decToHex[Math.floor(r * 255)] + decToHex[Math.floor(g * 255)] + decToHex[Math.floor(b * 255)]; } function hueToRgb(m1, m2, h) { if (h < 0) h++; if (h > 1) h--; if (6 * h < 1) return m1 + (m2 - m1) * 6 * h; else if (2 * h < 1) return m2; else if (3 * h < 2) return m1 + (m2 - m1) * (2 / 3 - h) * 6; else return m1; } var processStyleCache = {}; function processStyle(styleString) { if (styleString in processStyleCache) { return processStyleCache[styleString]; } var str, alpha = 1; styleString = String(styleString); if (styleString.charAt(0) == '#') { str = styleString; } else if (/^rgb/.test(styleString)) { var parts = getRgbHslContent(styleString); var str = '#', n; for (var i = 0; i < 3; i++) { if (parts[i].indexOf('%') != -1) { n = Math.floor(percent(parts[i]) * 255); } else { n = +parts[i]; } str += decToHex[clamp(n, 0, 255)]; } alpha = +parts[3]; } else if (/^hsl/.test(styleString)) { var parts = getRgbHslContent(styleString); str = hslToRgb(parts); alpha = parts[3]; } else { str = colorData[styleString] || styleString; } return processStyleCache[styleString] = {color: str, alpha: alpha}; } var DEFAULT_STYLE = { style: 'normal', variant: 'normal', weight: 'normal', size: 10, family: 'sans-serif' }; // Internal text style cache var fontStyleCache = {}; function processFontStyle(styleString) { if (fontStyleCache[styleString]) { return fontStyleCache[styleString]; } var el = document.createElement('div'); var style = el.style; try { style.font = styleString; } catch (ex) { // Ignore failures to set to invalid font. } return fontStyleCache[styleString] = { style: style.fontStyle || DEFAULT_STYLE.style, variant: style.fontVariant || DEFAULT_STYLE.variant, weight: style.fontWeight || DEFAULT_STYLE.weight, size: style.fontSize || DEFAULT_STYLE.size, family: style.fontFamily || DEFAULT_STYLE.family }; } function getComputedStyle(style, element) { var computedStyle = {}; for (var p in style) { computedStyle[p] = style[p]; } // Compute the size var canvasFontSize = parseFloat(element.currentStyle.fontSize), fontSize = parseFloat(style.size); if (typeof style.size == 'number') { computedStyle.size = style.size; } else if (style.size.indexOf('px') != -1) { computedStyle.size = fontSize; } else if (style.size.indexOf('em') != -1) { computedStyle.size = canvasFontSize * fontSize; } else if(style.size.indexOf('%') != -1) { computedStyle.size = (canvasFontSize / 100) * fontSize; } else if (style.size.indexOf('pt') != -1) { computedStyle.size = fontSize / .75; } else { computedStyle.size = canvasFontSize; } // Different scaling between normal text and VML text. This was found using // trial and error to get the same size as non VML text. computedStyle.size *= 0.981; return computedStyle; } function buildStyle(style) { return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + style.size + 'px ' + style.family; } var lineCapMap = { 'butt': 'flat', 'round': 'round' }; function processLineCap(lineCap) { return lineCapMap[lineCap] || 'square'; } /** * This class implements CanvasRenderingContext2D interface as described by * the WHATWG. * @param {HTMLElement} canvasElement The element that the 2D context should * be associated with */ function CanvasRenderingContext2D_(canvasElement) { this.m_ = createMatrixIdentity(); this.mStack_ = []; this.aStack_ = []; this.currentPath_ = []; // Canvas context properties this.strokeStyle = '#000'; this.fillStyle = '#000'; this.lineWidth = 1; this.lineJoin = 'miter'; this.lineCap = 'butt'; this.miterLimit = Z * 1; this.globalAlpha = 1; this.font = '10px sans-serif'; this.textAlign = 'left'; this.textBaseline = 'alphabetic'; this.canvas = canvasElement; var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + canvasElement.clientHeight + 'px;overflow:hidden;position:absolute'; var el = canvasElement.ownerDocument.createElement('div'); el.style.cssText = cssText; canvasElement.appendChild(el); var overlayEl = el.cloneNode(false); // Use a non transparent background. overlayEl.style.backgroundColor = 'red'; overlayEl.style.filter = 'alpha(opacity=0)'; canvasElement.appendChild(overlayEl); this.element_ = el; this.arcScaleX_ = 1; this.arcScaleY_ = 1; this.lineScale_ = 1; } var contextPrototype = CanvasRenderingContext2D_.prototype; contextPrototype.clearRect = function() { if (this.textMeasureEl_) { this.textMeasureEl_.removeNode(true); this.textMeasureEl_ = null; } this.element_.innerHTML = ''; }; contextPrototype.beginPath = function() { // TODO: Branch current matrix so that save/restore has no effect // as per safari docs. this.currentPath_ = []; }; contextPrototype.moveTo = function(aX, aY) { var p = getCoords(this, aX, aY); this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); this.currentX_ = p.x; this.currentY_ = p.y; }; contextPrototype.lineTo = function(aX, aY) { var p = getCoords(this, aX, aY); this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); this.currentX_ = p.x; this.currentY_ = p.y; }; contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) { var p = getCoords(this, aX, aY); var cp1 = getCoords(this, aCP1x, aCP1y); var cp2 = getCoords(this, aCP2x, aCP2y); bezierCurveTo(this, cp1, cp2, p); }; // Helper function that takes the already fixed cordinates. function bezierCurveTo(self, cp1, cp2, p) { self.currentPath_.push({ type: 'bezierCurveTo', cp1x: cp1.x, cp1y: cp1.y, cp2x: cp2.x, cp2y: cp2.y, x: p.x, y: p.y }); self.currentX_ = p.x; self.currentY_ = p.y; } contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { // the following is lifted almost directly from // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes var cp = getCoords(this, aCPx, aCPy); var p = getCoords(this, aX, aY); var cp1 = { x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) }; var cp2 = { x: cp1.x + (p.x - this.currentX_) / 3.0, y: cp1.y + (p.y - this.currentY_) / 3.0 }; bezierCurveTo(this, cp1, cp2, p); }; contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) { aRadius *= Z; var arcType = aClockwise ? 'at' : 'wa'; var xStart = aX + mc(aStartAngle) * aRadius - Z2; var yStart = aY + ms(aStartAngle) * aRadius - Z2; var xEnd = aX + mc(aEndAngle) * aRadius - Z2; var yEnd = aY + ms(aEndAngle) * aRadius - Z2; // IE won't render arches drawn counter clockwise if xStart == xEnd. if (xStart == xEnd && !aClockwise) { xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something // that can be represented in binary } var p = getCoords(this, aX, aY); var pStart = getCoords(this, xStart, yStart); var pEnd = getCoords(this, xEnd, yEnd); this.currentPath_.push({type: arcType, x: p.x, y: p.y, radius: aRadius, xStart: pStart.x, yStart: pStart.y, xEnd: pEnd.x, yEnd: pEnd.y}); }; contextPrototype.rect = function(aX, aY, aWidth, aHeight) { this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); }; contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { var oldPath = this.currentPath_; this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.stroke(); this.currentPath_ = oldPath; }; contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { var oldPath = this.currentPath_; this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.fill(); this.currentPath_ = oldPath; }; contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { var gradient = new CanvasGradient_('gradient'); gradient.x0_ = aX0; gradient.y0_ = aY0; gradient.x1_ = aX1; gradient.y1_ = aY1; return gradient; }; contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) { var gradient = new CanvasGradient_('gradientradial'); gradient.x0_ = aX0; gradient.y0_ = aY0; gradient.r0_ = aR0; gradient.x1_ = aX1; gradient.y1_ = aY1; gradient.r1_ = aR1; return gradient; }; contextPrototype.drawImage = function(image, var_args) { var dx, dy, dw, dh, sx, sy, sw, sh; // to find the original width we overide the width and height var oldRuntimeWidth = image.runtimeStyle.width; var oldRuntimeHeight = image.runtimeStyle.height; image.runtimeStyle.width = 'auto'; image.runtimeStyle.height = 'auto'; // get the original size var w = image.width; var h = image.height; // and remove overides image.runtimeStyle.width = oldRuntimeWidth; image.runtimeStyle.height = oldRuntimeHeight; if (arguments.length == 3) { dx = arguments[1]; dy = arguments[2]; sx = sy = 0; sw = dw = w; sh = dh = h; } else if (arguments.length == 5) { dx = arguments[1]; dy = arguments[2]; dw = arguments[3]; dh = arguments[4]; sx = sy = 0; sw = w; sh = h; } else if (arguments.length == 9) { sx = arguments[1]; sy = arguments[2]; sw = arguments[3]; sh = arguments[4]; dx = arguments[5]; dy = arguments[6]; dw = arguments[7]; dh = arguments[8]; } else { throw Error('Invalid number of arguments'); } var d = getCoords(this, dx, dy); var w2 = sw / 2; var h2 = sh / 2; var vmlStr = []; var W = 10; var H = 10; // For some reason that I've now forgotten, using divs didn't work vmlStr.push(' <g_vml_:group', ' coordsize="', Z * W, ',', Z * H, '"', ' coordorigin="0,0"' , ' style="width:', W, 'px;height:', H, 'px;position:absolute;'); // If filters are necessary (rotation exists), create them // filters are bog-slow, so only create them if abbsolutely necessary // The following check doesn't account for skews (which don't exist // in the canvas spec (yet) anyway. if (this.m_[0][0] != 1 || this.m_[0][1] || this.m_[1][1] != 1 || this.m_[1][0]) { var filter = []; // Note the 12/21 reversal filter.push('M11=', this.m_[0][0], ',', 'M12=', this.m_[1][0], ',', 'M21=', this.m_[0][1], ',', 'M22=', this.m_[1][1], ',', 'Dx=', mr(d.x / Z), ',', 'Dy=', mr(d.y / Z), ''); // Bounding box calculation (need to minimize displayed area so that // filters don't waste time on unused pixels. var max = d; var c2 = getCoords(this, dx + dw, dy); var c3 = getCoords(this, dx, dy + dh); var c4 = getCoords(this, dx + dw, dy + dh); max.x = m.max(max.x, c2.x, c3.x, c4.x); max.y = m.max(max.y, c2.y, c3.y, c4.y); vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z), 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(', filter.join(''), ", sizingmethod='clip');"); } else { vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;'); } vmlStr.push(' ">' , '<g_vml_:image src="', image.src, '"', ' style="width:', Z * dw, 'px;', ' height:', Z * dh, 'px"', ' cropleft="', sx / w, '"', ' croptop="', sy / h, '"', ' cropright="', (w - sx - sw) / w, '"', ' cropbottom="', (h - sy - sh) / h, '"', ' />', '</g_vml_:group>'); this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); }; contextPrototype.stroke = function(aFill) { var W = 10; var H = 10; // Divide the shape into chunks if it's too long because IE has a limit // somewhere for how long a VML shape can be. This simple division does // not work with fills, only strokes, unfortunately. var chunkSize = 5000; var min = {x: null, y: null}; var max = {x: null, y: null}; for (var j = 0; j < this.currentPath_.length; j += chunkSize) { var lineStr = []; var lineOpen = false; lineStr.push('<g_vml_:shape', ' filled="', !!aFill, '"', ' style="position:absolute;width:', W, 'px;height:', H, 'px;"', ' coordorigin="0,0"', ' coordsize="', Z * W, ',', Z * H, '"', ' stroked="', !aFill, '"', ' path="'); var newSeq = false; for (var i = j; i < Math.min(j + chunkSize, this.currentPath_.length); i++) { if (i % chunkSize == 0 && i > 0) { // move into position for next chunk lineStr.push(' m ', mr(this.currentPath_[i-1].x), ',', mr(this.currentPath_[i-1].y)); } var p = this.currentPath_[i]; var c; switch (p.type) { case 'moveTo': c = p; lineStr.push(' m ', mr(p.x), ',', mr(p.y)); break; case 'lineTo': lineStr.push(' l ', mr(p.x), ',', mr(p.y)); break; case 'close': lineStr.push(' x '); p = null; break; case 'bezierCurveTo': lineStr.push(' c ', mr(p.cp1x), ',', mr(p.cp1y), ',', mr(p.cp2x), ',', mr(p.cp2y), ',', mr(p.x), ',', mr(p.y)); break; case 'at': case 'wa': lineStr.push(' ', p.type, ' ', mr(p.x - this.arcScaleX_ * p.radius), ',', mr(p.y - this.arcScaleY_ * p.radius), ' ', mr(p.x + this.arcScaleX_ * p.radius), ',', mr(p.y + this.arcScaleY_ * p.radius), ' ', mr(p.xStart), ',', mr(p.yStart), ' ', mr(p.xEnd), ',', mr(p.yEnd)); break; } // TODO: Following is broken for curves due to // move to proper paths. // Figure out dimensions so we can do gradient fills // properly if (p) { if (min.x == null || p.x < min.x) { min.x = p.x; } if (max.x == null || p.x > max.x) { max.x = p.x; } if (min.y == null || p.y < min.y) { min.y = p.y; } if (max.y == null || p.y > max.y) { max.y = p.y; } } } lineStr.push(' ">'); if (!aFill) { appendStroke(this, lineStr); } else { appendFill(this, lineStr, min, max); } lineStr.push('</g_vml_:shape>'); this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); } }; function appendStroke(ctx, lineStr) { var a = processStyle(ctx.strokeStyle); var color = a.color; var opacity = a.alpha * ctx.globalAlpha; var lineWidth = ctx.lineScale_ * ctx.lineWidth; // VML cannot correctly render a line if the width is less than 1px. // In that case, we dilute the color to make the line look thinner. if (lineWidth < 1) { opacity *= lineWidth; } lineStr.push( '<g_vml_:stroke', ' opacity="', opacity, '"', ' joinstyle="', ctx.lineJoin, '"', ' miterlimit="', ctx.miterLimit, '"', ' endcap="', processLineCap(ctx.lineCap), '"', ' weight="', lineWidth, 'px"', ' color="', color, '" />' ); } function appendFill(ctx, lineStr, min, max) { var fillStyle = ctx.fillStyle; var arcScaleX = ctx.arcScaleX_; var arcScaleY = ctx.arcScaleY_; var width = max.x - min.x; var height = max.y - min.y; if (fillStyle instanceof CanvasGradient_) { // TODO: Gradients transformed with the transformation matrix. var angle = 0; var focus = {x: 0, y: 0}; // additional offset var shift = 0; // scale factor for offset var expansion = 1; if (fillStyle.type_ == 'gradient') { var x0 = fillStyle.x0_ / arcScaleX; var y0 = fillStyle.y0_ / arcScaleY; var x1 = fillStyle.x1_ / arcScaleX; var y1 = fillStyle.y1_ / arcScaleY; var p0 = getCoords(ctx, x0, y0); var p1 = getCoords(ctx, x1, y1); var dx = p1.x - p0.x; var dy = p1.y - p0.y; angle = Math.atan2(dx, dy) * 180 / Math.PI; // The angle should be a non-negative number. if (angle < 0) { angle += 360; } // Very small angles produce an unexpected result because they are // converted to a scientific notation string. if (angle < 1e-6) { angle = 0; } } else { var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_); focus = { x: (p0.x - min.x) / width, y: (p0.y - min.y) / height }; width /= arcScaleX * Z; height /= arcScaleY * Z; var dimension = m.max(width, height); shift = 2 * fillStyle.r0_ / dimension; expansion = 2 * fillStyle.r1_ / dimension - shift; } // We need to sort the color stops in ascending order by offset, // otherwise IE won't interpret it correctly. var stops = fillStyle.colors_; stops.sort(function(cs1, cs2) { return cs1.offset - cs2.offset; }); var length = stops.length; var color1 = stops[0].color; var color2 = stops[length - 1].color; var opacity1 = stops[0].alpha * ctx.globalAlpha; var opacity2 = stops[length - 1].alpha * ctx.globalAlpha; var colors = []; for (var i = 0; i < length; i++) { var stop = stops[i]; colors.push(stop.offset * expansion + shift + ' ' + stop.color); } // When colors attribute is used, the meanings of opacity and o:opacity2 // are reversed. lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"', ' method="none" focus="100%"', ' color="', color1, '"', ' color2="', color2, '"', ' colors="', colors.join(','), '"', ' opacity="', opacity2, '"', ' g_o_:opacity2="', opacity1, '"', ' angle="', angle, '"', ' focusposition="', focus.x, ',', focus.y, '" />'); } else if (fillStyle instanceof CanvasPattern_) { if (width && height) { var deltaLeft = -min.x; var deltaTop = -min.y; lineStr.push('<g_vml_:fill', ' position="', deltaLeft / width * arcScaleX * arcScaleX, ',', deltaTop / height * arcScaleY * arcScaleY, '"', ' type="tile"', // TODO: Figure out the correct size to fit the scale. //' size="', w, 'px ', h, 'px"', ' src="', fillStyle.src_, '" />'); } } else { var a = processStyle(ctx.fillStyle); var color = a.color; var opacity = a.alpha * ctx.globalAlpha; lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />'); } } contextPrototype.fill = function() { this.stroke(true); }; contextPrototype.closePath = function() { this.currentPath_.push({type: 'close'}); }; function getCoords(ctx, aX, aY) { var m = ctx.m_; return { x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 }; }; contextPrototype.save = function() { var o = {}; copyState(this, o); this.aStack_.push(o); this.mStack_.push(this.m_); this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); }; contextPrototype.restore = function() { if (this.aStack_.length) { copyState(this.aStack_.pop(), this); this.m_ = this.mStack_.pop(); } }; function matrixIsFinite(m) { return isFinite(m[0][0]) && isFinite(m[0][1]) && isFinite(m[1][0]) && isFinite(m[1][1]) && isFinite(m[2][0]) && isFinite(m[2][1]); } function setM(ctx, m, updateLineScale) { if (!matrixIsFinite(m)) { return; } ctx.m_ = m; if (updateLineScale) { // Get the line scale. // Determinant of this.m_ means how much the area is enlarged by the // transformation. So its square root can be used as a scale factor // for width. var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; ctx.lineScale_ = sqrt(abs(det)); } } contextPrototype.translate = function(aX, aY) { var m1 = [ [1, 0, 0], [0, 1, 0], [aX, aY, 1] ]; setM(this, matrixMultiply(m1, this.m_), false); }; contextPrototype.rotate = function(aRot) { var c = mc(aRot); var s = ms(aRot); var m1 = [ [c, s, 0], [-s, c, 0], [0, 0, 1] ]; setM(this, matrixMultiply(m1, this.m_), false); }; contextPrototype.scale = function(aX, aY) { this.arcScaleX_ *= aX; this.arcScaleY_ *= aY; var m1 = [ [aX, 0, 0], [0, aY, 0], [0, 0, 1] ]; setM(this, matrixMultiply(m1, this.m_), true); }; contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { var m1 = [ [m11, m12, 0], [m21, m22, 0], [dx, dy, 1] ]; setM(this, matrixMultiply(m1, this.m_), true); }; contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { var m = [ [m11, m12, 0], [m21, m22, 0], [dx, dy, 1] ]; setM(this, m, true); }; /** * The text drawing function. * The maxWidth argument isn't taken in account, since no browser supports * it yet. */ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { var m = this.m_, delta = 1000, left = 0, right = delta, offset = {x: 0, y: 0}, lineStr = []; var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_); var fontStyleString = buildStyle(fontStyle); var elementStyle = this.element_.currentStyle; var textAlign = this.textAlign.toLowerCase(); switch (textAlign) { case 'left': case 'center': case 'right': break; case 'end': textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; break; case 'start': textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; break; default: textAlign = 'left'; } // 1.75 is an arbitrary number, as there is no info about the text baseline switch (this.textBaseline) { case 'hanging': case 'top': offset.y = fontStyle.size / 1.75; break; case 'middle': break; default: case null: case 'alphabetic': case 'ideographic': case 'bottom': offset.y = -fontStyle.size / 2.25; break; } switch(textAlign) { case 'right': left = delta; right = 0.05; break; case 'center': left = right = delta / 2; break; } var d = getCoords(this, x + offset.x, y + offset.y); lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ', ' coordsize="100 100" coordorigin="0 0"', ' filled="', !stroke, '" stroked="', !!stroke, '" style="position:absolute;width:1px;height:1px;">'); if (stroke) { appendStroke(this, lineStr); } else { // TODO: Fix the min and max params. appendFill(this, lineStr, {x: -left, y: 0}, {x: right, y: fontStyle.size}); } var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z); lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ', ' offset="', skewOffset, '" origin="', left ,' 0" />', '<g_vml_:path textpathok="true" />', '<g_vml_:textpath on="true" string="', encodeHtmlAttribute(text), '" style="v-text-align:', textAlign, ';font:', encodeHtmlAttribute(fontStyleString), '" /></g_vml_:line>'); this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); }; contextPrototype.fillText = function(text, x, y, maxWidth) { this.drawText_(text, x, y, maxWidth, false); }; contextPrototype.strokeText = function(text, x, y, maxWidth) { this.drawText_(text, x, y, maxWidth, true); }; contextPrototype.measureText = function(text) { if (!this.textMeasureEl_) { var s = '<span style="position:absolute;' + 'top:-20000px;left:0;padding:0;margin:0;border:none;' + 'white-space:pre;"></span>'; this.element_.insertAdjacentHTML('beforeEnd', s); this.textMeasureEl_ = this.element_.lastChild; } var doc = this.element_.ownerDocument; this.textMeasureEl_.innerHTML = ''; this.textMeasureEl_.style.font = this.font; // Don't use innerHTML or innerText because they allow markup/whitespace. this.textMeasureEl_.appendChild(doc.createTextNode(text)); return {width: this.textMeasureEl_.offsetWidth}; }; /******** STUBS ********/ contextPrototype.clip = function() { // TODO: Implement }; contextPrototype.arcTo = function() { // TODO: Implement }; contextPrototype.createPattern = function(image, repetition) { return new CanvasPattern_(image, repetition); }; // Gradient / Pattern Stubs function CanvasGradient_(aType) { this.type_ = aType; this.x0_ = 0; this.y0_ = 0; this.r0_ = 0; this.x1_ = 0; this.y1_ = 0; this.r1_ = 0; this.colors_ = []; } CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { aColor = processStyle(aColor); this.colors_.push({offset: aOffset, color: aColor.color, alpha: aColor.alpha}); }; function CanvasPattern_(image, repetition) { assertImageIsValid(image); switch (repetition) { case 'repeat': case null: case '': this.repetition_ = 'repeat'; break case 'repeat-x': case 'repeat-y': case 'no-repeat': this.repetition_ = repetition; break; default: throwException('SYNTAX_ERR'); } this.src_ = image.src; this.width_ = image.width; this.height_ = image.height; } function throwException(s) { throw new DOMException_(s); } function assertImageIsValid(img) { if (!img || img.nodeType != 1 || img.tagName != 'IMG') { throwException('TYPE_MISMATCH_ERR'); } if (img.readyState != 'complete') { throwException('INVALID_STATE_ERR'); } } function DOMException_(s) { this.code = this[s]; this.message = s +': DOM Exception ' + this.code; } var p = DOMException_.prototype = new Error; p.INDEX_SIZE_ERR = 1; p.DOMSTRING_SIZE_ERR = 2; p.HIERARCHY_REQUEST_ERR = 3; p.WRONG_DOCUMENT_ERR = 4; p.INVALID_CHARACTER_ERR = 5; p.NO_DATA_ALLOWED_ERR = 6; p.NO_MODIFICATION_ALLOWED_ERR = 7; p.NOT_FOUND_ERR = 8; p.NOT_SUPPORTED_ERR = 9; p.INUSE_ATTRIBUTE_ERR = 10; p.INVALID_STATE_ERR = 11; p.SYNTAX_ERR = 12; p.INVALID_MODIFICATION_ERR = 13; p.NAMESPACE_ERR = 14; p.INVALID_ACCESS_ERR = 15; p.VALIDATION_ERR = 16; p.TYPE_MISMATCH_ERR = 17; // set up externs G_vmlCanvasManager = G_vmlCanvasManager_; CanvasRenderingContext2D = CanvasRenderingContext2D_; CanvasGradient = CanvasGradient_; CanvasPattern = CanvasPattern_; DOMException = DOMException_; })(); } // if jquery.flot.selection.js000060400000031512152526040300011343 0ustar00/* Flot plugin for selecting regions of a plot. Copyright (c) 2007-2014 IOLA and Ole Laursen. Licensed under the MIT license. The plugin supports these options: selection: { mode: null or "x" or "y" or "xy", color: color, shape: "round" or "miter" or "bevel", minSize: number of pixels } Selection support is enabled by setting the mode to one of "x", "y" or "xy". In "x" mode, the user will only be able to specify the x range, similarly for "y" mode. For "xy", the selection becomes a rectangle where both ranges can be specified. "color" is color of the selection (if you need to change the color later on, you can get to it with plot.getOptions().selection.color). "shape" is the shape of the corners of the selection. "minSize" is the minimum size a selection can be in pixels. This value can be customized to determine the smallest size a selection can be and still have the selection rectangle be displayed. When customizing this value, the fact that it refers to pixels, not axis units must be taken into account. Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 minute, setting "minSize" to 1 will not make the minimum selection size 1 minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent "plotunselected" events from being fired when the user clicks the mouse without dragging. When selection support is enabled, a "plotselected" event will be emitted on the DOM element you passed into the plot function. The event handler gets a parameter with the ranges selected on the axes, like this: placeholder.on( "plotselected", function( event, ranges ) { alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) // similar for yaxis - with multiple axes, the extra ones are in // x2axis, x3axis, ... }); The "plotselected" event is only fired when the user has finished making the selection. A "plotselecting" event is fired during the process with the same parameters as the "plotselected" event, in case you want to know what's happening while it's happening, A "plotunselected" event with no arguments is emitted when the user clicks the mouse to remove the selection. As stated above, setting "minSize" to 0 will destroy this behavior. The plugin allso adds the following methods to the plot object: - setSelection( ranges, preventEvent ) Set the selection rectangle. The passed in ranges is on the same form as returned in the "plotselected" event. If the selection mode is "x", you should put in either an xaxis range, if the mode is "y" you need to put in an yaxis range and both xaxis and yaxis if the selection mode is "xy", like this: setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); setSelection will trigger the "plotselected" event when called. If you don't want that to happen, e.g. if you're inside a "plotselected" handler, pass true as the second parameter. If you are using multiple axes, you can specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of xaxis, the plugin picks the first one it sees. - clearSelection( preventEvent ) Clear the selection rectangle. Pass in true to avoid getting a "plotunselected" event. - getSelection() Returns the current selection in the same format as the "plotselected" event. If there's currently no selection, the function returns null. */ (function ($) { function init(plot) { var selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1}, show: false, active: false }; // FIXME: The drag handling implemented here should be // abstracted out, there's some similar code from a library in // the navigation plugin, this should be massaged a bit to fit // the Flot cases here better and reused. Doing this would // make this plugin much slimmer. var savedhandlers = {}; var mouseUpHandler = null; function onMouseMove(e) { if (selection.active) { updateSelection(e); plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); } } function onMouseDown(e) { if (e.which != 1) // only accept left-click return; // cancel out any text selections document.body.focus(); // prevent text selection and drag in old-school browsers if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { savedhandlers.onselectstart = document.onselectstart; document.onselectstart = function () { return false; }; } if (document.ondrag !== undefined && savedhandlers.ondrag == null) { savedhandlers.ondrag = document.ondrag; document.ondrag = function () { return false; }; } setSelectionPos(selection.first, e); selection.active = true; // this is a bit silly, but we have to use a closure to be // able to whack the same handler again mouseUpHandler = function (e) { onMouseUp(e); }; $(document).one("mouseup", mouseUpHandler); } function onMouseUp(e) { mouseUpHandler = null; // revert drag stuff for old-school browsers if (document.onselectstart !== undefined) document.onselectstart = savedhandlers.onselectstart; if (document.ondrag !== undefined) document.ondrag = savedhandlers.ondrag; // no more dragging selection.active = false; updateSelection(e); if (selectionIsSane()) triggerSelectedEvent(); else { // this counts as a clear plot.getPlaceholder().trigger("plotunselected", [ ]); plot.getPlaceholder().trigger("plotselecting", [ null ]); } return false; } function getSelection() { if (!selectionIsSane()) return null; if (!selection.show) return null; var r = {}, c1 = selection.first, c2 = selection.second; $.each(plot.getAxes(), function (name, axis) { if (axis.used) { var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; } }); return r; } function triggerSelectedEvent() { var r = getSelection(); plot.getPlaceholder().trigger("plotselected", [ r ]); // backwards-compat stuff, to be removed in future if (r.xaxis && r.yaxis) plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); } function clamp(min, value, max) { return value < min ? min: (value > max ? max: value); } function setSelectionPos(pos, e) { var o = plot.getOptions(); var offset = plot.getPlaceholder().offset(); var plotOffset = plot.getPlotOffset(); pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); if (o.selection.mode == "y") pos.x = pos == selection.first ? 0 : plot.width(); if (o.selection.mode == "x") pos.y = pos == selection.first ? 0 : plot.height(); } function updateSelection(pos) { if (pos.pageX == null) return; setSelectionPos(selection.second, pos); if (selectionIsSane()) { selection.show = true; plot.triggerRedrawOverlay(); } else clearSelection(true); } function clearSelection(preventEvent) { if (selection.show) { selection.show = false; plot.triggerRedrawOverlay(); if (!preventEvent) plot.getPlaceholder().trigger("plotunselected", [ ]); } } // function taken from markings support in Flot function extractRange(ranges, coord) { var axis, from, to, key, axes = plot.getAxes(); for (var k in axes) { axis = axes[k]; if (axis.direction == coord) { key = coord + axis.n + "axis"; if (!ranges[key] && axis.n == 1) key = coord + "axis"; // support x1axis as xaxis if (ranges[key]) { from = ranges[key].from; to = ranges[key].to; break; } } } // backwards-compat stuff - to be removed in future if (!ranges[key]) { axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; from = ranges[coord + "1"]; to = ranges[coord + "2"]; } // auto-reverse as an added bonus if (from != null && to != null && from > to) { var tmp = from; from = to; to = tmp; } return { from: from, to: to, axis: axis }; } function setSelection(ranges, preventEvent) { var axis, range, o = plot.getOptions(); if (o.selection.mode == "y") { selection.first.x = 0; selection.second.x = plot.width(); } else { range = extractRange(ranges, "x"); selection.first.x = range.axis.p2c(range.from); selection.second.x = range.axis.p2c(range.to); } if (o.selection.mode == "x") { selection.first.y = 0; selection.second.y = plot.height(); } else { range = extractRange(ranges, "y"); selection.first.y = range.axis.p2c(range.from); selection.second.y = range.axis.p2c(range.to); } selection.show = true; plot.triggerRedrawOverlay(); if (!preventEvent && selectionIsSane()) triggerSelectedEvent(); } function selectionIsSane() { var minSize = plot.getOptions().selection.minSize; return Math.abs(selection.second.x - selection.first.x) >= minSize && Math.abs(selection.second.y - selection.first.y) >= minSize; } plot.clearSelection = clearSelection; plot.setSelection = setSelection; plot.getSelection = getSelection; plot.hooks.bindEvents.push(function(plot, eventHolder) { var o = plot.getOptions(); if (o.selection.mode != null) { eventHolder.mousemove(onMouseMove); eventHolder.mousedown(onMouseDown); } }); plot.hooks.drawOverlay.push(function (plot, ctx) { // draw selection if (selection.show && selectionIsSane()) { var plotOffset = plot.getPlotOffset(); var o = plot.getOptions(); ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); var c = $.color.parse(o.selection.color); ctx.strokeStyle = c.scale('a', 0.8).toString(); ctx.lineWidth = 1; ctx.lineJoin = o.selection.shape; ctx.fillStyle = c.scale('a', 0.4).toString(); var x = Math.min(selection.first.x, selection.second.x) + 0.5, y = Math.min(selection.first.y, selection.second.y) + 0.5, w = Math.abs(selection.second.x - selection.first.x) - 1, h = Math.abs(selection.second.y - selection.first.y) - 1; ctx.fillRect(x, y, w, h); ctx.strokeRect(x, y, w, h); ctx.restore(); } }); plot.hooks.shutdown.push(function (plot, eventHolder) { eventHolder.off("mousemove", onMouseMove); eventHolder.off("mousedown", onMouseDown); if (mouseUpHandler) $(document).off("mouseup", mouseUpHandler); }); } $.plot.plugins.push({ init: init, options: { selection: { mode: null, // one of null, "x", "y" or "xy" color: "#e8cfac", shape: "round", // one of "round", "miter", or "bevel" minSize: 5 // minimum number of pixels } }, name: 'selection', version: '1.1' }); })(jQuery); jquery.tflot.js000060400000046121152526040300007545 0ustar00/** * Librairie tFlot pour jQuery et jQuery.flot * Licence GNU/GPL - Matthieu Marcillaud * Version 1.4.0 */ (function($){ /** * Des variables a garder globalement * * collections : stockage de l'ensemble de toutes les valeurs de tous les graphs et leurs options * collectionsActives : stockage des series actives * plots : stockage des graphiques * vignettes : stockage des vignettes * idGraph : identifiant unique pour tous les graphs */ collections = []; collectionsActives = []; plots = []; vignettes = []; vignettesSelection = []; idGraph = 0; /* * Fait un graphique d'un tableau donne * $("table.graph").tFlot(); * necessite la librairie "jQuery flot". * http://code.google.com/p/flot/ */ $.fn.tFlot = function(settings) { var options, flot; options = { width:'500px', height:'250px', parse:{ orientation:'row', // 'column' : tableaux verticaux par defaut... axeOnTitle:false // les coordonnees x d'axe sont donnes dans l'attribut title du <th> et non dans le <th> ? }, legendeExterne:false, legendeActions:false, // ne fonctionne qu'avec l'option legende externe modeDate:false, // pour calculer les timestamp automatiquement moyenneGlissante:{ show:false, // pour calculer une moyenne glissante automatiquement plage:7 // plage de glissement (nombre impair !) }, grille:{weekend:false}, infobulle:{ show:false, serie_color:false // utiliser comme couleur de fond la même couleur que les lignes du graph }, zoom:false, vignette:{ show:false, width:'160px', height:'100px' }, flot:{ legend:{ show:true, container:null, labelFormatter:null, noColumns: 3 }, bars: {fill:false}, yaxis: { min: 0 }, selection: { mode: "x" } } } $.extend(true, options, settings); $(this).each(function(){ // identifiant unique pour tous les graphs // creer les cadres // .graphique // .graphResult // .graphInfos // .graphLegend // .graphOverview $(this).hide().wrap("<div class='graphique' id='graphique"+idGraph+"'></div>"); graphique = $(this).parent(); values = parseTable(this, options.parse); $.extend(true, values.options, options.flot); graph = $("<div class='graphResult' style='width:" + options.width + ";height:" + options.height + ";'></div>").appendTo(graphique); graph.wrap("<div class='graphResult-wrap'></div>"); gInfo = $("<div class='graphInfo'></div>").appendTo(graphique); // legende en dehors du dessin ? if (options.legendeExterne) { legend = $("<div class='graphLegend' id='grapLegend"+idGraph+"'></div>").appendTo(gInfo); values.options.legend.container = legend; } // legende avec items clicables pour desactiver certaines series if (options.legendeExterne && options.legendeActions) { values.options.legend.labelFormatter = function(label) { return '<a href="#label">' + label + '</a>'; } } // si mode time, on calcule des timestamp // les series sont alors de la forme [[timestamp, valeur],...] // et pas besoin de ticks declare if (options.modeDate) { timestamps = []; // calcul des timestamps $.each(values.options.xaxis.ticks, function(i, val){ timestamps.push([val[0], (new Date(val[1])).getTime()]); }); // les remettre dans les series $.each(values.series, function(i, val){ data = []; $.each(val.data, function (j, d){ data.push([timestamps[j][1], d[1]]); }); values.series[i].data = data; }); // plus besoin du ticks // mais toujours besoin des valeurs completes... values.options.xaxis = $.extend(true, { mode: "time", timeformat: "%d/%m/%y" }, values.options.xaxis, {ticks: null} ); if (options.grille.weekend) { values.options.grid = { markings: weekendAreas } } if (options.grille.years) { values.options.grid = { markings: yearsArea } } } // en cas de moyenne glissante, on la calcule if (options.moyenneGlissante.show) { values.series = moyenneGlissante(values.series, options.moyenneGlissante); } // si infobulles, les ajouter if (options.infobulle.show) { $.extend(true, options.infobulle, {date:options.modeDate}); infobulle($('#graphique'+idGraph), options.infobulle); $.extend(true, values.options, { grid:{hoverable:true} }); } // dessiner plots[idGraph] = $.plot(graph, values.series, values.options); // prevoir les actions sur les labels if (options.legendeExterne && options.legendeActions) { $.extend(values.options, {legend:{container:null, show:false}}); actionsLegendes($('#graphique'+idGraph)); } // ajouter une mini vue si demandee if (options.vignette.show) { $("<div class='graphVignette' id='#graphVignette"+idGraph + "' style='width:" + options.vignette.width + ";height:" + options.vignette.height + ";'></div>").appendTo(gInfo); creerVignette($('#graphique'+idGraph), values.series, values.options, options.vignette); } // autoriser les zoom if (options.zoom) { zoomGraphique($('#graphique'+idGraph)); } // stocker les valeurs collections.push({id:idGraph, values:values}); // sources collectionsActives = $.extend(true, {}, collections); // affiches ++idGraph; }); /* * Prendre une table HTML * et calculer les donnees d'un graph jQuery.plot */ function parseTable(table, settings){ var options; flot = []; options = { ticks:[], // [1:"label 1", 2:"label 2"] orientation:'row', // 'column' ticksReels:[], // on sauve les vraies donnees pour les infobulles (1 janvier 2008) et non le code de date (1/1/2008) axeOnTitle:false, defaultSerie:{ bars: { barWidth: 0.9, align: "center", show:true, fill:false }, lines: { show:false, fill:false } } } $.extend(options, settings); row = (options.orientation == 'row'); // // recuperer les points d'axes // // // Une fonction pour simplifier la recup // function getValue(element) { if (options.axeOnTitle) { return element.attr('title'); } else { return element.text(); } } axe=0; if (row) { // dans le th de chaque tr $(table).find('tr:not(:first)').each(function(){ $(this).find('th:first').each(function(){ options.ticks.push([++axe, getValue($(this))]); options.ticksReels.push([axe, $(this).text()]); }); }); } else { // dans les th du premier tr $(table).find('tr:first th:not(:first)').each(function(){ options.ticks.push([++axe, getValue($(this))]); options.ticksReels.push([axe, $(this).text()]); }); } // // recuperer les noms de series // axe = (axe ? 1 : 0); if (row) { // si axes definis, on saute une ligne if (axe) { columns = $(table).find('tr:first th:not(:first)'); } else { columns = $(table).find('tr:first th'); } // chaque colonne est une serie for(i=0; i<columns.length; i++){ cpt=0, data=[]; th = $(table).find('tr:first th:eq(' + (i + axe) + ')'); label = th.text(); serieOptions = optionsCss(th); $(table).find('tr td:nth-child(' + (i + 1 + axe) +')').each(function(){ val = parseFloat($(this).text()); data.push( [++cpt, val] ); }); serie = {label:label, data:data}; $.extend(serie, serieOptions); flot.push(serie); } } else { // si axes definis, on saute une colonne if (axe) { rows = $(table).find('tr:not(:first)'); } else { rows = $(table).find('tr'); } // chaque ligne est une serie rows.each(function(){ cpt=0, data=[]; th = $(this).find('th'); label = th.text(); serieOptions = optionsCss(th); // recuperer les valeurs $(this).find('td').each(function(){ val = parseFloat($(this).text()); data.push( [++cpt, val] ); }); serie = {label:label, data:data}; $.extend(serie, serieOptions); flot.push(serie); }); } // // mettre les options dans les series // color=0; $.each(flot, function(i, serie) { if (!serie.color) { serie.color = color++; } serie = $.extend(true, {}, options.defaultSerie, serie); flot[i] = serie; }); opt = { xaxis: {} } if (options.ticks.length) { opt.xaxis.ticks = options.ticks; opt.xaxis.ticksReels = options.ticksReels; } return {series:flot, options:opt}; } /* * * Recuperer les options en fonctions de CSS * */ function optionsCss(element) { var options = {}; $element = $(element); if ($element.data('serie') == 'line') { $.extend(true, options, { lines:{show:true}, bars:{show:false}, points:{show:false} }); } if ($element.data('serie') == 'bar') { $.extend(true, options, { lines:{show:false}, bars:{show:true}, points:{show:false} }); } if ($element.data('serie') == 'lineBar') { $.extend(true, options, { lines:{show:true,steps:true}, bars:{show:false}, points:{show:false} }); } if ($element.data('fill')) { $.extend(true, options, { lines:{ fill:true, fillColor: { colors: [ { opacity: 0.9 }, { opacity: 0.9 } ] } }, bars:{ fill:true, fillColor: { colors: [ { opacity: 0.9 }, { opacity: 0.9 } ] } } }); } if (color = $element.data('color')) { options.color = color; } return options; } /* * * calcul d'une moyenne glissante * */ function moyenneGlissante(lesSeries, settings) { var options; options = { plage: 7, texte:"Moyenne glissante" } $.extend(options, settings); g = options.plage; series = []; color = 0; $.each(lesSeries, function(i, val){ // recuperer le numero de couleur max color = ParseInt(Math.max(color,val.color)); data = [], moy = []; $.each(val.data, function (j, d){ // ajout du nouvel element // et retrait du trop vieux moy.push(parseInt(d[1])); if (moy.length>=g) { moy.shift();} // calcul de la somme et ajout de la moyenne for(var k=0,sum=0;k<moy.length;sum+=moy[k++]); data.push([d[0], Math.round(sum/moy.length)]); }); serieG = $.extend(true, {}, val, { data:data, label:val.label + " ("+options.texte+")", lines:{ show:true, fill:false }, bars:{show:false} }); series.push(val); series.push(serieG); }); // remettre les couleurs $.each(series, function(i, val) { if (!val.color) { val.color = color++; } }); return series; } // // Permettre de cacher certaines series // function actionsLegendes(graph) { // actions sur les items de legende // pour masquer / afficher certaines series // a ne charger qu'une fois par graph !!! $(graph).find('.legendLabel a').click(function(){ tr = $(this).parent().prev('.legendColorBox').toggleClass('cacher').parent(); master = tr.closest('.graphique'); pid = master.attr('id').substr(9); // enlever 'graphique' var seriesActives = []; tr.find('.legendColorBox:not(.cacher)').each(function(){ nom = $(this).next('.legendLabel').find('a').text(); n = collections[pid].values.series.length; for(i=0;i<n;i++) { if (collections[pid].values.series[i].label == nom) { seriesActives.push(collections[pid].values.series[i]); break; } } }); collectionsActives[pid].values.series = seriesActives; $.plot(master.find('.graphResult'), seriesActives, collections[pid].values.options); // vignettes if (master.find('.graphVignette').length) { creerVignette(master, seriesActives, collections[pid].values.options); } }); } // // Afficher une miniature // function creerVignette(graphique, series, optionsParents, settings) { var options; options = { show:true, zoom:true, flot:{ legend: { show: false }, shadowSize: 0, lines: { show: true, lineWidth: 1 }, grid: { color: "#999", hoverable:null }, selection: { mode: "x" }, xaxis:{min:null, max:null}, yaxis:{min:null, max:null} } }; $.extend(true, options, settings); options.flot = $.extend(true, {}, optionsParents, options.flot); // demarrer la vignette vignette = $(graphique).find('.graphVignette'); pid = vignette.closest('.graphique').attr('id').substr(9); vignettes[pid] = $.plot(vignette, series, options.flot); if (vignettesSelection[pid] !== undefined) { vignettes[pid].setSelection(vignettesSelection[pid]); } } // // Permettre le zoom sur le graphique // et sur la miniature // function zoomGraphique(graphique) { pid = $(graphique).attr('id').substr(9); $(graphique).find('.graphResult').on("plotselected", function (event, ranges) { graph = $(event.target); pid = graph.closest('.graphique').attr('id').substr(9); // clamp the zooming to prevent eternal zoom if (ranges.xaxis.to - ranges.xaxis.from < 0.00001) ranges.xaxis.to = ranges.xaxis.from + 0.00001; if (ranges.yaxis.to - ranges.yaxis.from < 0.00001) ranges.yaxis.to = ranges.yaxis.from + 0.00001; // do the zooming // et sauver les parametres du zoom plots[pid] = $.plot(graph, collectionsActives[pid].values.series, $.extend(true, collections[pid].values.options, { xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to }, yaxis: { min: ranges.yaxis.from, max: ranges.yaxis.to } })); // don't fire event on the overview to prevent eternal loop if (vignettes[pid] !== undefined) { vignettes[pid].setSelection(ranges, true); } }); // raz sur double clic $(graphique).find('.graphResult').dblclick(function (event) { var graphique; graphique = $(event.target).closest('.graphique'); pid = graphique.attr('id').substr(9); vignettesSelection[pid] = undefined; if (vignettes[pid] != undefined) { vignettes[pid].clearSelection(); } plots[pid] = $.plot(graphique.find('.graphResult'), collectionsActives[pid].values.series, $.extend(true, collections[pid].values.options, { xaxis: { min: null, max: null }, yaxis: { min: null, max: null } })); }); // si une vignette est presente vignette = $(graphique).find('.graphVignette'); if (vignette.length) { // zoom depuis la miniature vignette.on("plotselected", function (event, ranges) { graph = $(event.target); pid = graph.closest('.graphique').attr('id').substr(9); vignettesSelection[pid] = ranges; plots[pid].setSelection(ranges); }); // raz depuis la miniature sur double clic vignette.dblclick(function (event) { var graphique; graphique = $(event.target).closest('.graphique'); pid = graphique.attr('id').substr(9); vignettesSelection[pid] = undefined; plots[pid] = $.plot(graphique.find('.graphResult'), collectionsActives[pid].values.series, $.extend(true, collections[pid].values.options, { xaxis: { min: null, max: null }, yaxis: { min: null, max: null } })); }); } } /* * * Infobulles * */ var previousPoint = null; function infobulle(graph, settings) { var options; options = { show:true }; $.extend(true, options, settings); $(graph).on("plothover", function (event, pos, item) { $("#x").text(pos.x.toFixed(2)); $("#y").text(pos.y.toFixed(2)); graph = $(event.target); pid = graph.closest('.graphique').attr('id').substr(9); if (options.show) { if (item) { if (previousPoint != item.datapoint) { previousPoint = item.datapoint; $("#tooltip").remove(); var x = item.datapoint[0], y = item.datapoint[1]; var color = ''; if(options.serie_color){ color = item.series.color; } x = collectionsActives[pid].values.options.xaxis.ticksReels[item.dataIndex][1]; showTooltip(item.pageX, item.pageY, item.series.label + " [" + x + "] = " + y, color); } } else { $("#tooltip").remove(); previousPoint = null; } } }); } } // Adapte du site de Flot (exemple de visites) // helper for returning the weekends in a period function weekendAreas(axes) { var markings = []; var heure = 60 * 60 * 1000; var jour = 24 * heure; // les week ends // go to the first Saturday var d = new Date(axes.xaxis.min); d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7)) d.setUTCSeconds(0); d.setUTCMinutes(0); d.setUTCHours(0); var i = d.getTime(); do { markings.push({ xaxis: { from: i, to: i + 2*jour }, color: '#e8e8e8' }); i += 7*jour; } while (i < axes.xaxis.max); // les mois et les ans... $.each(yearsArea(axes), function(i,j){ markings.push(j); }); return markings; } // une grille pour afficher les mois et les ans... function yearsArea(axes){ var markings = []; var heure = 60 * 60 * 1000; var jour = 24 * heure; var width_year = jour; if (axes.xaxis.options.minTickSize[1]=="month") width_year = 30.4*jour; // les mois et les ans... d = new Date(axes.xaxis.min); y = d.getUTCFullYear(); m = d.getUTCMonth(); if (++m == 12) {m=0; ++y;} d = new Date(Date.UTC(y,m,1,0,0,0)); do { i = d.getTime(); if (m == 0) {couleur = '#CA5F18';} else {couleur = '#D7C2AF'; } markings.push({ xaxis: { from: i, to: i + (m==0?width_year:jour)}, color: couleur }); if (++m == 12) {m=0; ++y;} d = new Date(Date.UTC(y,m,1,0,0,0)); } while (d.getTime() < axes.xaxis.max); return markings; } /** * Exemple adapte du site de Flot (exemple d'interactions) * montrer les informations des points * * Arguments : * x (Float) Coordonnee longitudinale de la bulle * y (Float) Coordonnee latitudinale de la bulle * contents (String) Le contenu de la bulle * color La couleur de fond de l'infobulles */ function showTooltip(x, y, contents, color) { $('<div id="tooltip">' + contents + '</div>').css( { top: y + 5, left: x + 5, opacity: 0.80, background:color }).addClass('tooltip_statistiques').appendTo("body").fadeIn(200); } // copie de la fonction de jquery.flot.js // pour utilisation dans infobulle function formatDate(d, fmt, monthNames) { var leftPad = function(n) { n = "" + n; return n.length == 1 ? "0" + n : n; }; var r = []; var escape = false; if (monthNames == null) monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; for (var i = 0; i < fmt.length; ++i) { var c = fmt.charAt(i); if (escape) { switch (c) { case 'h': c = "" + d.getUTCHours(); break; case 'H': c = leftPad(d.getUTCHours()); break; case 'M': c = leftPad(d.getUTCMinutes()); break; case 'S': c = leftPad(d.getUTCSeconds()); break; case 'd': c = "" + d.getUTCDate(); break; case 'm': c = "" + (d.getUTCMonth() + 1); break; case 'y': c = "" + d.getUTCFullYear(); break; case 'b': c = "" + monthNames[d.getUTCMonth()]; break; } r.push(c); escape = false; } else { if (c == "%") escape = true; else r.push(c); } } return r.join(""); } })(jQuery); jquery.flot.js000060400000360115152526040300007363 0ustar00/* Javascript plotting library for jQuery, version 0.8.3. Copyright (c) 2007-2014 IOLA and Ole Laursen. Licensed under the MIT license. */ // first an inline dependency, jquery.colorhelpers.js, we inline it here // for convenience /* Plugin for jQuery for working with colors. * * Version 1.1. * * Inspiration from jQuery color animation plugin by John Resig. * * Released under the MIT license by Ole Laursen, October 2009. * * Examples: * * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() * var c = $.color.extract($("#mydiv"), 'background-color'); * console.log(c.r, c.g, c.b, c.a); * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" * * Note that .scale() and .add() return the same modified object * instead of making a new one. * * V. 1.1: Fix error handling so e.g. parsing an empty string does * produce a color rather than just crashing. */ (function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/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))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/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))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); // the actual Flot code (function($) { // Cache the prototype hasOwnProperty for faster access var hasOwnProperty = Object.prototype.hasOwnProperty; // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM // operation produces the same effect as detach, i.e. removing the element // without touching its jQuery data. // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+. if (!$.fn.detach) { $.fn.detach = function() { return this.each(function() { if (this.parentNode) { this.parentNode.removeChild( this ); } }); }; } /////////////////////////////////////////////////////////////////////////// // The Canvas object is a wrapper around an HTML5 <canvas> tag. // // @constructor // @param {string} cls List of classes to apply to the canvas. // @param {element} container Element onto which to append the canvas. // // Requiring a container is a little iffy, but unfortunately canvas // operations don't work unless the canvas is attached to the DOM. function Canvas(cls, container) { var element = container.children("." + cls)[0]; if (element == null) { element = document.createElement("canvas"); element.className = cls; $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) .appendTo(container); // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas if (!element.getContext) { if (window.G_vmlCanvasManager) { element = window.G_vmlCanvasManager.initElement(element); } else { throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); } } } this.element = element; var context = this.context = element.getContext("2d"); // Determine the screen's ratio of physical to device-independent // pixels. This is the ratio between the canvas width that the browser // advertises and the number of pixels actually present in that space. // The iPhone 4, for example, has a device-independent width of 320px, // but its screen is actually 640px wide. It therefore has a pixel // ratio of 2, while most normal devices have a ratio of 1. var devicePixelRatio = window.devicePixelRatio || 1, backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; this.pixelRatio = devicePixelRatio / backingStoreRatio; // Size the canvas to match the internal dimensions of its container this.resize(container.width(), container.height()); // Collection of HTML div layers for text overlaid onto the canvas this.textContainer = null; this.text = {}; // Cache of text fragments and metrics, so we can avoid expensively // re-calculating them when the plot is re-rendered in a loop. this._textCache = {}; } // Resizes the canvas to the given dimensions. // // @param {number} width New width of the canvas, in pixels. // @param {number} width New height of the canvas, in pixels. Canvas.prototype.resize = function(width, height) { if (width <= 0 || height <= 0) { throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); } var element = this.element, context = this.context, pixelRatio = this.pixelRatio; // Resize the canvas, increasing its density based on the display's // pixel ratio; basically giving it more pixels without increasing the // size of its element, to take advantage of the fact that retina // displays have that many more pixels in the same advertised space. // Resizing should reset the state (excanvas seems to be buggy though) if (this.width != width) { element.width = width * pixelRatio; element.style.width = width + "px"; this.width = width; } if (this.height != height) { element.height = height * pixelRatio; element.style.height = height + "px"; this.height = height; } // Save the context, so we can reset in case we get replotted. The // restore ensure that we're really back at the initial state, and // should be safe even if we haven't saved the initial state yet. context.restore(); context.save(); // Scale the coordinate space to match the display density; so even though we // may have twice as many pixels, we still want lines and other drawing to // appear at the same size; the extra pixels will just make them crisper. context.scale(pixelRatio, pixelRatio); }; // Clears the entire canvas area, not including any overlaid HTML text Canvas.prototype.clear = function() { this.context.clearRect(0, 0, this.width, this.height); }; // Finishes rendering the canvas, including managing the text overlay. Canvas.prototype.render = function() { var cache = this._textCache; // For each text layer, add elements marked as active that haven't // already been rendered, and remove those that are no longer active. for (var layerKey in cache) { if (hasOwnProperty.call(cache, layerKey)) { var layer = this.getTextLayer(layerKey), layerCache = cache[layerKey]; layer.hide(); for (var styleKey in layerCache) { if (hasOwnProperty.call(layerCache, styleKey)) { var styleCache = layerCache[styleKey]; for (var key in styleCache) { if (hasOwnProperty.call(styleCache, key)) { var positions = styleCache[key].positions; for (var i = 0, position; position = positions[i]; i++) { if (position.active) { if (!position.rendered) { layer.append(position.element); position.rendered = true; } } else { positions.splice(i--, 1); if (position.rendered) { position.element.detach(); } } } if (positions.length == 0) { delete styleCache[key]; } } } } } layer.show(); } } }; // Creates (if necessary) and returns the text overlay container. // // @param {string} classes String of space-separated CSS classes used to // uniquely identify the text layer. // @return {object} The jQuery-wrapped text-layer div. Canvas.prototype.getTextLayer = function(classes) { var layer = this.text[classes]; // Create the text layer if it doesn't exist if (layer == null) { // Create the text layer container, if it doesn't exist if (this.textContainer == null) { this.textContainer = $("<div class='flot-text'></div>") .css({ position: "absolute", top: 0, left: 0, bottom: 0, right: 0, 'font-size': "smaller", color: "#545454" }) .insertAfter(this.element); } layer = this.text[classes] = $("<div></div>") .addClass(classes) .css({ position: "absolute", top: 0, left: 0, bottom: 0, right: 0 }) .appendTo(this.textContainer); } return layer; }; // Creates (if necessary) and returns a text info object. // // The object looks like this: // // { // width: Width of the text's wrapper div. // height: Height of the text's wrapper div. // element: The jQuery-wrapped HTML div containing the text. // positions: Array of positions at which this text is drawn. // } // // The positions array contains objects that look like this: // // { // active: Flag indicating whether the text should be visible. // rendered: Flag indicating whether the text is currently visible. // element: The jQuery-wrapped HTML div containing the text. // x: X coordinate at which to draw the text. // y: Y coordinate at which to draw the text. // } // // Each position after the first receives a clone of the original element. // // The idea is that that the width, height, and general 'identity' of the // text is constant no matter where it is placed; the placements are a // secondary property. // // Canvas maintains a cache of recently-used text info objects; getTextInfo // either returns the cached element or creates a new entry. // // @param {string} layer A string of space-separated CSS classes uniquely // identifying the layer containing this text. // @param {string} text Text string to retrieve info for. // @param {(string|object)=} font Either a string of space-separated CSS // classes or a font-spec object, defining the text's font and style. // @param {number=} angle Angle at which to rotate the text, in degrees. // Angle is currently unused, it will be implemented in the future. // @param {number=} width Maximum width of the text before it wraps. // @return {object} a text info object. Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { var textStyle, layerCache, styleCache, info; // Cast the value to a string, in case we were given a number or such text = "" + text; // If the font is a font-spec object, generate a CSS font definition if (typeof font === "object") { textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; } else { textStyle = font; } // Retrieve (or create) the cache for the text's layer and styles layerCache = this._textCache[layer]; if (layerCache == null) { layerCache = this._textCache[layer] = {}; } styleCache = layerCache[textStyle]; if (styleCache == null) { styleCache = layerCache[textStyle] = {}; } info = styleCache[text]; // If we can't find a matching element in our cache, create a new one if (info == null) { var element = $("<div></div>").html(text) .css({ position: "absolute", 'max-width': width, top: -9999 }) .appendTo(this.getTextLayer(layer)); if (typeof font === "object") { element.css({ font: textStyle, color: font.color }); } else if (typeof font === "string") { element.addClass(font); } info = styleCache[text] = { width: element.outerWidth(true), height: element.outerHeight(true), element: element, positions: [] }; element.detach(); } return info; }; // Adds a text string to the canvas text overlay. // // The text isn't drawn immediately; it is marked as rendering, which will // result in its addition to the canvas on the next render pass. // // @param {string} layer A string of space-separated CSS classes uniquely // identifying the layer containing this text. // @param {number} x X coordinate at which to draw the text. // @param {number} y Y coordinate at which to draw the text. // @param {string} text Text string to draw. // @param {(string|object)=} font Either a string of space-separated CSS // classes or a font-spec object, defining the text's font and style. // @param {number=} angle Angle at which to rotate the text, in degrees. // Angle is currently unused, it will be implemented in the future. // @param {number=} width Maximum width of the text before it wraps. // @param {string=} halign Horizontal alignment of the text; either "left", // "center" or "right". // @param {string=} valign Vertical alignment of the text; either "top", // "middle" or "bottom". Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { var info = this.getTextInfo(layer, text, font, angle, width), positions = info.positions; // Tweak the div's position to match the text's alignment if (halign == "center") { x -= info.width / 2; } else if (halign == "right") { x -= info.width; } if (valign == "middle") { y -= info.height / 2; } else if (valign == "bottom") { y -= info.height; } // Determine whether this text already exists at this position. // If so, mark it for inclusion in the next render pass. for (var i = 0, position; position = positions[i]; i++) { if (position.x == x && position.y == y) { position.active = true; return; } } // If the text doesn't exist at this position, create a new entry // For the very first position we'll re-use the original element, // while for subsequent ones we'll clone it. position = { active: true, rendered: false, element: positions.length ? info.element.clone() : info.element, x: x, y: y }; positions.push(position); // Move the element to its final position within the container position.element.css({ top: Math.round(y), left: Math.round(x), 'text-align': halign // In case the text wraps }); }; // Removes one or more text strings from the canvas text overlay. // // If no parameters are given, all text within the layer is removed. // // Note that the text is not immediately removed; it is simply marked as // inactive, which will result in its removal on the next render pass. // This avoids the performance penalty for 'clear and redraw' behavior, // where we potentially get rid of all text on a layer, but will likely // add back most or all of it later, as when redrawing axes, for example. // // @param {string} layer A string of space-separated CSS classes uniquely // identifying the layer containing this text. // @param {number=} x X coordinate of the text. // @param {number=} y Y coordinate of the text. // @param {string=} text Text string to remove. // @param {(string|object)=} font Either a string of space-separated CSS // classes or a font-spec object, defining the text's font and style. // @param {number=} angle Angle at which the text is rotated, in degrees. // Angle is currently unused, it will be implemented in the future. Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { if (text == null) { var layerCache = this._textCache[layer]; if (layerCache != null) { for (var styleKey in layerCache) { if (hasOwnProperty.call(layerCache, styleKey)) { var styleCache = layerCache[styleKey]; for (var key in styleCache) { if (hasOwnProperty.call(styleCache, key)) { var positions = styleCache[key].positions; for (var i = 0, position; position = positions[i]; i++) { position.active = false; } } } } } } } else { var positions = this.getTextInfo(layer, text, font, angle).positions; for (var i = 0, position; position = positions[i]; i++) { if (position.x == x && position.y == y) { position.active = false; } } } }; /////////////////////////////////////////////////////////////////////////// // The top-level container for the entire plot. function Plot(placeholder, data_, options_, plugins) { // data is on the form: // [ series1, series2 ... ] // where series is either just the data as [ [x1, y1], [x2, y2], ... ] // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } var series = [], options = { // the color theme used for graphs colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], legend: { show: true, noColumns: 1, // number of colums in legend table labelFormatter: null, // fn: string -> string labelBoxBorderColor: "#ccc", // border color for the little label boxes container: null, // container (as jQuery object) to put legend in, null means default on top of graph position: "ne", // position of default legend container within plot margin: 5, // distance from grid edge to default legend container within plot backgroundColor: null, // null means auto-detect backgroundOpacity: 0.85, // set to 0 to avoid background sorted: null // default to no legend sorting }, xaxis: { show: null, // null = auto-detect, true = always, false = never position: "bottom", // or "top" mode: null, // null or "time" font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } color: null, // base color, labels, ticks tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" transform: null, // null or f: number -> number to transform axis inverseTransform: null, // if transform is set, this should be the inverse function min: null, // min. value to show, null means set automatically max: null, // max. value to show, null means set automatically autoscaleMargin: null, // margin in % to add if auto-setting min/max ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks tickFormatter: null, // fn: number -> string labelWidth: null, // size of tick labels in pixels labelHeight: null, reserveSpace: null, // whether to reserve space even if axis isn't shown tickLength: null, // size in pixels of ticks, or "full" for whole line alignTicksWithAxis: null, // axis number or null for no sync tickDecimals: null, // no. of decimals, null means auto tickSize: null, // number or [number, "unit"] minTickSize: null // number or [number, "unit"] }, yaxis: { autoscaleMargin: 0.02, position: "left" // or "right" }, xaxes: [], yaxes: [], series: { points: { show: false, radius: 3, lineWidth: 2, // in pixels fill: true, fillColor: "#ffffff", symbol: "circle" // or callback }, lines: { // we don't put in show: false so we can see // whether lines were actively disabled lineWidth: 2, // in pixels fill: false, fillColor: null, steps: false // Omit 'zero', so we can later default its value to // match that of the 'fill' option. }, bars: { show: false, lineWidth: 2, // in pixels barWidth: 1, // in units of the x axis fill: true, fillColor: null, align: "left", // "left", "right", or "center" horizontal: false, zero: true }, shadowSize: 3, highlightColor: null }, grid: { show: true, aboveData: false, color: "#545454", // primary color used for outline and labels backgroundColor: null, // null for transparent, else color borderColor: null, // set if different from the grid color tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" margin: 0, // distance from the canvas edge to the grid labelMargin: 5, // in pixels axisMargin: 8, // in pixels borderWidth: 2, // in pixels minBorderMargin: null, // in pixels, null means taken from points radius markings: null, // array of ranges or fn: axes -> array of ranges markingsColor: "#f4f4f4", markingsLineWidth: 2, // interactive stuff clickable: false, hoverable: false, autoHighlight: true, // highlight in case mouse is near mouseActiveRadius: 10 // how far the mouse can be away to activate an item }, interaction: { redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow }, hooks: {} }, surface = null, // the canvas for the plot itself overlay = null, // canvas for interactive stuff on top of plot eventHolder = null, // jQuery object that events should be bound to ctx = null, octx = null, xaxes = [], yaxes = [], plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, plotWidth = 0, plotHeight = 0, hooks = { processOptions: [], processRawData: [], processDatapoints: [], processOffset: [], drawBackground: [], drawSeries: [], draw: [], bindEvents: [], drawOverlay: [], shutdown: [] }, plot = this; // public functions plot.setData = setData; plot.setupGrid = setupGrid; plot.draw = draw; plot.getPlaceholder = function() { return placeholder; }; plot.getCanvas = function() { return surface.element; }; plot.getPlotOffset = function() { return plotOffset; }; plot.width = function () { return plotWidth; }; plot.height = function () { return plotHeight; }; plot.offset = function () { var o = eventHolder.offset(); o.left += plotOffset.left; o.top += plotOffset.top; return o; }; plot.getData = function () { return series; }; plot.getAxes = function () { var res = {}, i; $.each(xaxes.concat(yaxes), function (_, axis) { if (axis) res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; }); return res; }; plot.getXAxes = function () { return xaxes; }; plot.getYAxes = function () { return yaxes; }; plot.c2p = canvasToAxisCoords; plot.p2c = axisToCanvasCoords; plot.getOptions = function () { return options; }; plot.highlight = highlight; plot.unhighlight = unhighlight; plot.triggerRedrawOverlay = triggerRedrawOverlay; plot.pointOffset = function(point) { return { left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) }; }; plot.shutdown = shutdown; plot.destroy = function () { shutdown(); placeholder.removeData("plot").empty(); series = []; options = null; surface = null; overlay = null; eventHolder = null; ctx = null; octx = null; xaxes = []; yaxes = []; hooks = null; highlights = []; plot = null; }; plot.resize = function () { var width = placeholder.width(), height = placeholder.height(); surface.resize(width, height); overlay.resize(width, height); }; // public attributes plot.hooks = hooks; // initialize initPlugins(plot); parseOptions(options_); setupCanvases(); setData(data_); setupGrid(); draw(); bindEvents(); function executeHooks(hook, args) { args = [plot].concat(args); for (var i = 0; i < hook.length; ++i) hook[i].apply(this, args); } function initPlugins() { // References to key classes, allowing plugins to modify them var classes = { Canvas: Canvas }; for (var i = 0; i < plugins.length; ++i) { var p = plugins[i]; p.init(plot, classes); if (p.options) $.extend(true, options, p.options); } } function parseOptions(opts) { $.extend(true, options, opts); // $.extend merges arrays, rather than replacing them. When less // colors are provided than the size of the default palette, we // end up with those colors plus the remaining defaults, which is // not expected behavior; avoid it by replacing them here. if (opts && opts.colors) { options.colors = opts.colors; } if (options.xaxis.color == null) options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); if (options.yaxis.color == null) options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; if (options.grid.borderColor == null) options.grid.borderColor = options.grid.color; if (options.grid.tickColor == null) options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); // Fill in defaults for axis options, including any unspecified // font-spec fields, if a font-spec was provided. // If no x/y axis options were provided, create one of each anyway, // since the rest of the code assumes that they exist. var i, axisOptions, axisCount, fontSize = placeholder.css("font-size"), fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13, fontDefaults = { style: placeholder.css("font-style"), size: Math.round(0.8 * fontSizeDefault), variant: placeholder.css("font-variant"), weight: placeholder.css("font-weight"), family: placeholder.css("font-family") }; axisCount = options.xaxes.length || 1; for (i = 0; i < axisCount; ++i) { axisOptions = options.xaxes[i]; if (axisOptions && !axisOptions.tickColor) { axisOptions.tickColor = axisOptions.color; } axisOptions = $.extend(true, {}, options.xaxis, axisOptions); options.xaxes[i] = axisOptions; if (axisOptions.font) { axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); if (!axisOptions.font.color) { axisOptions.font.color = axisOptions.color; } if (!axisOptions.font.lineHeight) { axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); } } } axisCount = options.yaxes.length || 1; for (i = 0; i < axisCount; ++i) { axisOptions = options.yaxes[i]; if (axisOptions && !axisOptions.tickColor) { axisOptions.tickColor = axisOptions.color; } axisOptions = $.extend(true, {}, options.yaxis, axisOptions); options.yaxes[i] = axisOptions; if (axisOptions.font) { axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); if (!axisOptions.font.color) { axisOptions.font.color = axisOptions.color; } if (!axisOptions.font.lineHeight) { axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); } } } // backwards compatibility, to be removed in future if (options.xaxis.noTicks && options.xaxis.ticks == null) options.xaxis.ticks = options.xaxis.noTicks; if (options.yaxis.noTicks && options.yaxis.ticks == null) options.yaxis.ticks = options.yaxis.noTicks; if (options.x2axis) { options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); options.xaxes[1].position = "top"; // Override the inherit to allow the axis to auto-scale if (options.x2axis.min == null) { options.xaxes[1].min = null; } if (options.x2axis.max == null) { options.xaxes[1].max = null; } } if (options.y2axis) { options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); options.yaxes[1].position = "right"; // Override the inherit to allow the axis to auto-scale if (options.y2axis.min == null) { options.yaxes[1].min = null; } if (options.y2axis.max == null) { options.yaxes[1].max = null; } } if (options.grid.coloredAreas) options.grid.markings = options.grid.coloredAreas; if (options.grid.coloredAreasColor) options.grid.markingsColor = options.grid.coloredAreasColor; if (options.lines) $.extend(true, options.series.lines, options.lines); if (options.points) $.extend(true, options.series.points, options.points); if (options.bars) $.extend(true, options.series.bars, options.bars); if (options.shadowSize != null) options.series.shadowSize = options.shadowSize; if (options.highlightColor != null) options.series.highlightColor = options.highlightColor; // save options on axes for future reference for (i = 0; i < options.xaxes.length; ++i) getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; for (i = 0; i < options.yaxes.length; ++i) getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; // add hooks from options for (var n in hooks) if (options.hooks[n] && options.hooks[n].length) hooks[n] = hooks[n].concat(options.hooks[n]); executeHooks(hooks.processOptions, [options]); } function setData(d) { series = parseData(d); fillInSeriesOptions(); processData(); } function parseData(d) { var res = []; for (var i = 0; i < d.length; ++i) { var s = $.extend(true, {}, options.series); if (d[i].data != null) { s.data = d[i].data; // move the data instead of deep-copy delete d[i].data; $.extend(true, s, d[i]); d[i].data = s.data; } else s.data = d[i]; res.push(s); } return res; } function axisNumber(obj, coord) { var a = obj[coord + "axis"]; if (typeof a == "object") // if we got a real axis, extract number a = a.n; if (typeof a != "number") a = 1; // default to first axis return a; } function allAxes() { // return flat array without annoying null entries return $.grep(xaxes.concat(yaxes), function (a) { return a; }); } function canvasToAxisCoords(pos) { // return an object with x/y corresponding to all used axes var res = {}, i, axis; for (i = 0; i < xaxes.length; ++i) { axis = xaxes[i]; if (axis && axis.used) res["x" + axis.n] = axis.c2p(pos.left); } for (i = 0; i < yaxes.length; ++i) { axis = yaxes[i]; if (axis && axis.used) res["y" + axis.n] = axis.c2p(pos.top); } if (res.x1 !== undefined) res.x = res.x1; if (res.y1 !== undefined) res.y = res.y1; return res; } function axisToCanvasCoords(pos) { // get canvas coords from the first pair of x/y found in pos var res = {}, i, axis, key; for (i = 0; i < xaxes.length; ++i) { axis = xaxes[i]; if (axis && axis.used) { key = "x" + axis.n; if (pos[key] == null && axis.n == 1) key = "x"; if (pos[key] != null) { res.left = axis.p2c(pos[key]); break; } } } for (i = 0; i < yaxes.length; ++i) { axis = yaxes[i]; if (axis && axis.used) { key = "y" + axis.n; if (pos[key] == null && axis.n == 1) key = "y"; if (pos[key] != null) { res.top = axis.p2c(pos[key]); break; } } } return res; } function getOrCreateAxis(axes, number) { if (!axes[number - 1]) axes[number - 1] = { n: number, // save the number for future reference direction: axes == xaxes ? "x" : "y", options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) }; return axes[number - 1]; } function fillInSeriesOptions() { var neededColors = series.length, maxIndex = -1, i; // Subtract the number of series that already have fixed colors or // color indexes from the number that we still need to generate. for (i = 0; i < series.length; ++i) { var sc = series[i].color; if (sc != null) { neededColors--; if (typeof sc == "number" && sc > maxIndex) { maxIndex = sc; } } } // If any of the series have fixed color indexes, then we need to // generate at least as many colors as the highest index. if (neededColors <= maxIndex) { neededColors = maxIndex + 1; } // Generate all the colors, using first the option colors and then // variations on those colors once they're exhausted. var c, colors = [], colorPool = options.colors, colorPoolSize = colorPool.length, variation = 0; for (i = 0; i < neededColors; i++) { c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); // Each time we exhaust the colors in the pool we adjust // a scaling factor used to produce more variations on // those colors. The factor alternates negative/positive // to produce lighter/darker colors. // Reset the variation after every few cycles, or else // it will end up producing only white or black colors. if (i % colorPoolSize == 0 && i) { if (variation >= 0) { if (variation < 0.5) { variation = -variation - 0.2; } else variation = 0; } else variation = -variation; } colors[i] = c.scale('rgb', 1 + variation); } // Finalize the series options, filling in their colors var colori = 0, s; for (i = 0; i < series.length; ++i) { s = series[i]; // assign colors if (s.color == null) { s.color = colors[colori].toString(); ++colori; } else if (typeof s.color == "number") s.color = colors[s.color].toString(); // turn on lines automatically in case nothing is set if (s.lines.show == null) { var v, show = true; for (v in s) if (s[v] && s[v].show) { show = false; break; } if (show) s.lines.show = true; } // If nothing was provided for lines.zero, default it to match // lines.fill, since areas by default should extend to zero. if (s.lines.zero == null) { s.lines.zero = !!s.lines.fill; } // setup axes s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); } } function processData() { var topSentry = Number.POSITIVE_INFINITY, bottomSentry = Number.NEGATIVE_INFINITY, fakeInfinity = Number.MAX_VALUE, i, j, k, m, length, s, points, ps, x, y, axis, val, f, p, data, format; function updateAxis(axis, min, max) { if (min < axis.datamin && min != -fakeInfinity) axis.datamin = min; if (max > axis.datamax && max != fakeInfinity) axis.datamax = max; } $.each(allAxes(), function (_, axis) { // init axis axis.datamin = topSentry; axis.datamax = bottomSentry; axis.used = false; }); for (i = 0; i < series.length; ++i) { s = series[i]; s.datapoints = { points: [] }; executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); } // first pass: clean and copy data for (i = 0; i < series.length; ++i) { s = series[i]; data = s.data; format = s.datapoints.format; if (!format) { format = []; // find out how to copy format.push({ x: true, number: true, required: true }); format.push({ y: true, number: true, required: true }); if (s.bars.show || (s.lines.show && s.lines.fill)) { var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); if (s.bars.horizontal) { delete format[format.length - 1].y; format[format.length - 1].x = true; } } s.datapoints.format = format; } if (s.datapoints.pointsize != null) continue; // already filled in s.datapoints.pointsize = format.length; ps = s.datapoints.pointsize; points = s.datapoints.points; var insertSteps = s.lines.show && s.lines.steps; s.xaxis.used = s.yaxis.used = true; for (j = k = 0; j < data.length; ++j, k += ps) { p = data[j]; var nullify = p == null; if (!nullify) { for (m = 0; m < ps; ++m) { val = p[m]; f = format[m]; if (f) { if (f.number && val != null) { val = +val; // convert to number if (isNaN(val)) val = null; else if (val == Infinity) val = fakeInfinity; else if (val == -Infinity) val = -fakeInfinity; } if (val == null) { if (f.required) nullify = true; if (f.defaultValue != null) val = f.defaultValue; } } points[k + m] = val; } } if (nullify) { for (m = 0; m < ps; ++m) { val = points[k + m]; if (val != null) { f = format[m]; // extract min/max info if (f.autoscale !== false) { if (f.x) { updateAxis(s.xaxis, val, val); } if (f.y) { updateAxis(s.yaxis, val, val); } } } points[k + m] = null; } } else { // a little bit of line specific stuff that // perhaps shouldn't be here, but lacking // better means... if (insertSteps && k > 0 && points[k - ps] != null && points[k - ps] != points[k] && points[k - ps + 1] != points[k + 1]) { // copy the point to make room for a middle point for (m = 0; m < ps; ++m) points[k + ps + m] = points[k + m]; // middle point has same y points[k + 1] = points[k - ps + 1]; // we've added a point, better reflect that k += ps; } } } } // give the hooks a chance to run for (i = 0; i < series.length; ++i) { s = series[i]; executeHooks(hooks.processDatapoints, [ s, s.datapoints]); } // second pass: find datamax/datamin for auto-scaling for (i = 0; i < series.length; ++i) { s = series[i]; points = s.datapoints.points; ps = s.datapoints.pointsize; format = s.datapoints.format; var xmin = topSentry, ymin = topSentry, xmax = bottomSentry, ymax = bottomSentry; for (j = 0; j < points.length; j += ps) { if (points[j] == null) continue; for (m = 0; m < ps; ++m) { val = points[j + m]; f = format[m]; if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) continue; if (f.x) { if (val < xmin) xmin = val; if (val > xmax) xmax = val; } if (f.y) { if (val < ymin) ymin = val; if (val > ymax) ymax = val; } } } if (s.bars.show) { // make sure we got room for the bar on the dancing floor var delta; switch (s.bars.align) { case "left": delta = 0; break; case "right": delta = -s.bars.barWidth; break; default: delta = -s.bars.barWidth / 2; } if (s.bars.horizontal) { ymin += delta; ymax += delta + s.bars.barWidth; } else { xmin += delta; xmax += delta + s.bars.barWidth; } } updateAxis(s.xaxis, xmin, xmax); updateAxis(s.yaxis, ymin, ymax); } $.each(allAxes(), function (_, axis) { if (axis.datamin == topSentry) axis.datamin = null; if (axis.datamax == bottomSentry) axis.datamax = null; }); } function setupCanvases() { // Make sure the placeholder is clear of everything except canvases // from a previous plot in this container that we'll try to re-use. placeholder.css("padding", 0) // padding messes up the positioning .children().filter(function(){ return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base'); }).remove(); if (placeholder.css("position") == 'static') placeholder.css("position", "relative"); // for positioning labels and overlay surface = new Canvas("flot-base", placeholder); overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features ctx = surface.context; octx = overlay.context; // define which element we're listening for events on eventHolder = $(overlay.element).off(); // If we're re-using a plot object, shut down the old one var existing = placeholder.data("plot"); if (existing) { existing.shutdown(); overlay.clear(); } // save in case we get replotted placeholder.data("plot", plot); } function bindEvents() { // bind events if (options.grid.hoverable) { eventHolder.mousemove(onMouseMove); // Use bind, rather than .mouseleave, because we officially // still support jQuery 1.2.6, which doesn't define a shortcut // for mouseenter or mouseleave. This was a bug/oversight that // was fixed somewhere around 1.3.x. We can return to using // .mouseleave when we drop support for 1.2.6. eventHolder.on("mouseleave", onMouseLeave); } if (options.grid.clickable) eventHolder.click(onClick); executeHooks(hooks.bindEvents, [eventHolder]); } function shutdown() { if (redrawTimeout) clearTimeout(redrawTimeout); eventHolder.off("mousemove", onMouseMove); eventHolder.off("mouseleave", onMouseLeave); eventHolder.off("click", onClick); executeHooks(hooks.shutdown, [eventHolder]); } function setTransformationHelpers(axis) { // set helper functions on the axis, assumes plot area // has been computed already function identity(x) { return x; } var s, m, t = axis.options.transform || identity, it = axis.options.inverseTransform; // precompute how much the axis is scaling a point // in canvas space if (axis.direction == "x") { s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); m = Math.min(t(axis.max), t(axis.min)); } else { s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); s = -s; m = Math.max(t(axis.max), t(axis.min)); } // data point to canvas coordinate if (t == identity) // slight optimization axis.p2c = function (p) { return (p - m) * s; }; else axis.p2c = function (p) { return (t(p) - m) * s; }; // canvas coordinate to data point if (!it) axis.c2p = function (c) { return m + c / s; }; else axis.c2p = function (c) { return it(m + c / s); }; } function measureTickLabels(axis) { var opts = axis.options, ticks = axis.ticks || [], labelWidth = opts.labelWidth || 0, labelHeight = opts.labelHeight || 0, maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null), legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, font = opts.font || "flot-tick-label tickLabel"; for (var i = 0; i < ticks.length; ++i) { var t = ticks[i]; if (!t.label) continue; var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); labelWidth = Math.max(labelWidth, info.width); labelHeight = Math.max(labelHeight, info.height); } axis.labelWidth = opts.labelWidth || labelWidth; axis.labelHeight = opts.labelHeight || labelHeight; } function allocateAxisBoxFirstPhase(axis) { // find the bounding box of the axis by looking at label // widths/heights and ticks, make room by diminishing the // plotOffset; this first phase only looks at one // dimension per axis, the other dimension depends on the // other axes so will have to wait var lw = axis.labelWidth, lh = axis.labelHeight, pos = axis.options.position, isXAxis = axis.direction === "x", tickLength = axis.options.tickLength, axisMargin = options.grid.axisMargin, padding = options.grid.labelMargin, innermost = true, outermost = true, first = true, found = false; // Determine the axis's position in its direction and on its side $.each(isXAxis ? xaxes : yaxes, function(i, a) { if (a && (a.show || a.reserveSpace)) { if (a === axis) { found = true; } else if (a.options.position === pos) { if (found) { outermost = false; } else { innermost = false; } } if (!found) { first = false; } } }); // The outermost axis on each side has no margin if (outermost) { axisMargin = 0; } // The ticks for the first axis in each direction stretch across if (tickLength == null) { tickLength = first ? "full" : 5; } if (!isNaN(+tickLength)) padding += +tickLength; if (isXAxis) { lh += padding; if (pos == "bottom") { plotOffset.bottom += lh + axisMargin; axis.box = { top: surface.height - plotOffset.bottom, height: lh }; } else { axis.box = { top: plotOffset.top + axisMargin, height: lh }; plotOffset.top += lh + axisMargin; } } else { lw += padding; if (pos == "left") { axis.box = { left: plotOffset.left + axisMargin, width: lw }; plotOffset.left += lw + axisMargin; } else { plotOffset.right += lw + axisMargin; axis.box = { left: surface.width - plotOffset.right, width: lw }; } } // save for future reference axis.position = pos; axis.tickLength = tickLength; axis.box.padding = padding; axis.innermost = innermost; } function allocateAxisBoxSecondPhase(axis) { // now that all axis boxes have been placed in one // dimension, we can set the remaining dimension coordinates if (axis.direction == "x") { axis.box.left = plotOffset.left - axis.labelWidth / 2; axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; } else { axis.box.top = plotOffset.top - axis.labelHeight / 2; axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; } } function adjustLayoutForThingsStickingOut() { // possibly adjust plot offset to ensure everything stays // inside the canvas and isn't clipped off var minMargin = options.grid.minBorderMargin, axis, i; // check stuff from the plot (FIXME: this should just read // a value from the series, otherwise it's impossible to // customize) if (minMargin == null) { minMargin = 0; for (i = 0; i < series.length; ++i) minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); } var margins = { left: minMargin, right: minMargin, top: minMargin, bottom: minMargin }; // check axis labels, note we don't check the actual // labels but instead use the overall width/height to not // jump as much around with replots $.each(allAxes(), function (_, axis) { if (axis.reserveSpace && axis.ticks && axis.ticks.length) { if (axis.direction === "x") { margins.left = Math.max(margins.left, axis.labelWidth / 2); margins.right = Math.max(margins.right, axis.labelWidth / 2); } else { margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2); margins.top = Math.max(margins.top, axis.labelHeight / 2); } } }); plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left)); plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right)); plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top)); plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom)); } function setupGrid() { var i, axes = allAxes(), showGrid = options.grid.show; // Initialize the plot's offset from the edge of the canvas for (var a in plotOffset) { var margin = options.grid.margin || 0; plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; } executeHooks(hooks.processOffset, [plotOffset]); // If the grid is visible, add its border width to the offset for (var a in plotOffset) { if(typeof(options.grid.borderWidth) == "object") { plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; } else { plotOffset[a] += showGrid ? options.grid.borderWidth : 0; } } $.each(axes, function (_, axis) { var axisOpts = axis.options; axis.show = axisOpts.show == null ? axis.used : axisOpts.show; axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace; setRange(axis); }); if (showGrid) { var allocatedAxes = $.grep(axes, function (axis) { return axis.show || axis.reserveSpace; }); $.each(allocatedAxes, function (_, axis) { // make the ticks setupTickGeneration(axis); setTicks(axis); snapRangeToTicks(axis, axis.ticks); // find labelWidth/Height for axis measureTickLabels(axis); }); // with all dimensions calculated, we can compute the // axis bounding boxes, start from the outside // (reverse order) for (i = allocatedAxes.length - 1; i >= 0; --i) allocateAxisBoxFirstPhase(allocatedAxes[i]); // make sure we've got enough space for things that // might stick out adjustLayoutForThingsStickingOut(); $.each(allocatedAxes, function (_, axis) { allocateAxisBoxSecondPhase(axis); }); } plotWidth = surface.width - plotOffset.left - plotOffset.right; plotHeight = surface.height - plotOffset.bottom - plotOffset.top; // now we got the proper plot dimensions, we can compute the scaling $.each(axes, function (_, axis) { setTransformationHelpers(axis); }); if (showGrid) { drawAxisLabels(); } insertLegend(); } function setRange(axis) { var opts = axis.options, min = +(opts.min != null ? opts.min : axis.datamin), max = +(opts.max != null ? opts.max : axis.datamax), delta = max - min; if (delta == 0.0) { // degenerate case var widen = max == 0 ? 1 : 0.01; if (opts.min == null) min -= widen; // always widen max if we couldn't widen min to ensure we // don't fall into min == max which doesn't work if (opts.max == null || opts.min != null) max += widen; } else { // consider autoscaling var margin = opts.autoscaleMargin; if (margin != null) { if (opts.min == null) { min -= delta * margin; // make sure we don't go below zero if all values // are positive if (min < 0 && axis.datamin != null && axis.datamin >= 0) min = 0; } if (opts.max == null) { max += delta * margin; if (max > 0 && axis.datamax != null && axis.datamax <= 0) max = 0; } } } axis.min = min; axis.max = max; } function setupTickGeneration(axis) { var opts = axis.options; // estimate number of ticks var noTicks; if (typeof opts.ticks == "number" && opts.ticks > 0) noTicks = opts.ticks; else // heuristic based on the model a*sqrt(x) fitted to // some data points that seemed reasonable noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); var delta = (axis.max - axis.min) / noTicks, dec = -Math.floor(Math.log(delta) / Math.LN10), maxDec = opts.tickDecimals; if (maxDec != null && dec > maxDec) { dec = maxDec; } var magn = Math.pow(10, -dec), norm = delta / magn, // norm is between 1.0 and 10.0 size; if (norm < 1.5) { size = 1; } else if (norm < 3) { size = 2; // special case for 2.5, requires an extra decimal if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { size = 2.5; ++dec; } } else if (norm < 7.5) { size = 5; } else { size = 10; } size *= magn; if (opts.minTickSize != null && size < opts.minTickSize) { size = opts.minTickSize; } axis.delta = delta; axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); axis.tickSize = opts.tickSize || size; // Time mode was moved to a plug-in in 0.8, and since so many people use it // we'll add an especially friendly reminder to make sure they included it. if (opts.mode == "time" && !axis.tickGenerator) { throw new Error("Time mode requires the flot.time plugin."); } // Flot supports base-10 axes; any other mode else is handled by a plug-in, // like flot.time.js. if (!axis.tickGenerator) { axis.tickGenerator = function (axis) { var ticks = [], start = floorInBase(axis.min, axis.tickSize), i = 0, v = Number.NaN, prev; do { prev = v; v = start + i * axis.tickSize; ticks.push(v); ++i; } while (v < axis.max && v != prev); return ticks; }; axis.tickFormatter = function (value, axis) { var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; var formatted = "" + Math.round(value * factor) / factor; // If tickDecimals was specified, ensure that we have exactly that // much precision; otherwise default to the value's own precision. if (axis.tickDecimals != null) { var decimal = formatted.indexOf("."); var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; if (precision < axis.tickDecimals) { return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); } } return formatted; }; } if ($.isFunction(opts.tickFormatter)) axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; if (opts.alignTicksWithAxis != null) { var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; if (otherAxis && otherAxis.used && otherAxis != axis) { // consider snapping min/max to outermost nice ticks var niceTicks = axis.tickGenerator(axis); if (niceTicks.length > 0) { if (opts.min == null) axis.min = Math.min(axis.min, niceTicks[0]); if (opts.max == null && niceTicks.length > 1) axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); } axis.tickGenerator = function (axis) { // copy ticks, scaled to this axis var ticks = [], v, i; for (i = 0; i < otherAxis.ticks.length; ++i) { v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); v = axis.min + v * (axis.max - axis.min); ticks.push(v); } return ticks; }; // we might need an extra decimal since forced // ticks don't necessarily fit naturally if (!axis.mode && opts.tickDecimals == null) { var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), ts = axis.tickGenerator(axis); // only proceed if the tick interval rounded // with an extra decimal doesn't give us a // zero at end if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) axis.tickDecimals = extraDec; } } } } function setTicks(axis) { var oticks = axis.options.ticks, ticks = []; if (oticks == null || (typeof oticks == "number" && oticks > 0)) ticks = axis.tickGenerator(axis); else if (oticks) { if ($.isFunction(oticks)) // generate the ticks ticks = oticks(axis); else ticks = oticks; } // clean up/labelify the supplied ticks, copy them over var i, v; axis.ticks = []; for (i = 0; i < ticks.length; ++i) { var label = null; var t = ticks[i]; if (typeof t == "object") { v = +t[0]; if (t.length > 1) label = t[1]; } else v = +t; if (label == null) label = axis.tickFormatter(v, axis); if (!isNaN(v)) axis.ticks.push({ v: v, label: label }); } } function snapRangeToTicks(axis, ticks) { if (axis.options.autoscaleMargin && ticks.length > 0) { // snap to ticks if (axis.options.min == null) axis.min = Math.min(axis.min, ticks[0].v); if (axis.options.max == null && ticks.length > 1) axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); } } function draw() { surface.clear(); executeHooks(hooks.drawBackground, [ctx]); var grid = options.grid; // draw background, if any if (grid.show && grid.backgroundColor) drawBackground(); if (grid.show && !grid.aboveData) { drawGrid(); } for (var i = 0; i < series.length; ++i) { executeHooks(hooks.drawSeries, [ctx, series[i]]); drawSeries(series[i]); } executeHooks(hooks.draw, [ctx]); if (grid.show && grid.aboveData) { drawGrid(); } surface.render(); // A draw implies that either the axes or data have changed, so we // should probably update the overlay highlights as well. triggerRedrawOverlay(); } function extractRange(ranges, coord) { var axis, from, to, key, axes = allAxes(); for (var i = 0; i < axes.length; ++i) { axis = axes[i]; if (axis.direction == coord) { key = coord + axis.n + "axis"; if (!ranges[key] && axis.n == 1) key = coord + "axis"; // support x1axis as xaxis if (ranges[key]) { from = ranges[key].from; to = ranges[key].to; break; } } } // backwards-compat stuff - to be removed in future if (!ranges[key]) { axis = coord == "x" ? xaxes[0] : yaxes[0]; from = ranges[coord + "1"]; to = ranges[coord + "2"]; } // auto-reverse as an added bonus if (from != null && to != null && from > to) { var tmp = from; from = to; to = tmp; } return { from: from, to: to, axis: axis }; } function drawBackground() { ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); ctx.fillRect(0, 0, plotWidth, plotHeight); ctx.restore(); } function drawGrid() { var i, axes, bw, bc; ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); // draw markings var markings = options.grid.markings; if (markings) { if ($.isFunction(markings)) { axes = plot.getAxes(); // xmin etc. is backwards compatibility, to be // removed in the future axes.xmin = axes.xaxis.min; axes.xmax = axes.xaxis.max; axes.ymin = axes.yaxis.min; axes.ymax = axes.yaxis.max; markings = markings(axes); } for (i = 0; i < markings.length; ++i) { var m = markings[i], xrange = extractRange(m, "x"), yrange = extractRange(m, "y"); // fill in missing if (xrange.from == null) xrange.from = xrange.axis.min; if (xrange.to == null) xrange.to = xrange.axis.max; if (yrange.from == null) yrange.from = yrange.axis.min; if (yrange.to == null) yrange.to = yrange.axis.max; // clip if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) continue; xrange.from = Math.max(xrange.from, xrange.axis.min); xrange.to = Math.min(xrange.to, xrange.axis.max); yrange.from = Math.max(yrange.from, yrange.axis.min); yrange.to = Math.min(yrange.to, yrange.axis.max); var xequal = xrange.from === xrange.to, yequal = yrange.from === yrange.to; if (xequal && yequal) { continue; } // then draw xrange.from = Math.floor(xrange.axis.p2c(xrange.from)); xrange.to = Math.floor(xrange.axis.p2c(xrange.to)); yrange.from = Math.floor(yrange.axis.p2c(yrange.from)); yrange.to = Math.floor(yrange.axis.p2c(yrange.to)); if (xequal || yequal) { var lineWidth = m.lineWidth || options.grid.markingsLineWidth, subPixel = lineWidth % 2 ? 0.5 : 0; ctx.beginPath(); ctx.strokeStyle = m.color || options.grid.markingsColor; ctx.lineWidth = lineWidth; if (xequal) { ctx.moveTo(xrange.to + subPixel, yrange.from); ctx.lineTo(xrange.to + subPixel, yrange.to); } else { ctx.moveTo(xrange.from, yrange.to + subPixel); ctx.lineTo(xrange.to, yrange.to + subPixel); } ctx.stroke(); } else { ctx.fillStyle = m.color || options.grid.markingsColor; ctx.fillRect(xrange.from, yrange.to, xrange.to - xrange.from, yrange.from - yrange.to); } } } // draw the ticks axes = allAxes(); bw = options.grid.borderWidth; for (var j = 0; j < axes.length; ++j) { var axis = axes[j], box = axis.box, t = axis.tickLength, x, y, xoff, yoff; if (!axis.show || axis.ticks.length == 0) continue; ctx.lineWidth = 1; // find the edges if (axis.direction == "x") { x = 0; if (t == "full") y = (axis.position == "top" ? 0 : plotHeight); else y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); } else { y = 0; if (t == "full") x = (axis.position == "left" ? 0 : plotWidth); else x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); } // draw tick bar if (!axis.innermost) { ctx.strokeStyle = axis.options.color; ctx.beginPath(); xoff = yoff = 0; if (axis.direction == "x") xoff = plotWidth + 1; else yoff = plotHeight + 1; if (ctx.lineWidth == 1) { if (axis.direction == "x") { y = Math.floor(y) + 0.5; } else { x = Math.floor(x) + 0.5; } } ctx.moveTo(x, y); ctx.lineTo(x + xoff, y + yoff); ctx.stroke(); } // draw ticks ctx.strokeStyle = axis.options.tickColor; ctx.beginPath(); for (i = 0; i < axis.ticks.length; ++i) { var v = axis.ticks[i].v; xoff = yoff = 0; if (isNaN(v) || v < axis.min || v > axis.max // skip those lying on the axes if we got a border || (t == "full" && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) && (v == axis.min || v == axis.max))) continue; if (axis.direction == "x") { x = axis.p2c(v); yoff = t == "full" ? -plotHeight : t; if (axis.position == "top") yoff = -yoff; } else { y = axis.p2c(v); xoff = t == "full" ? -plotWidth : t; if (axis.position == "left") xoff = -xoff; } if (ctx.lineWidth == 1) { if (axis.direction == "x") x = Math.floor(x) + 0.5; else y = Math.floor(y) + 0.5; } ctx.moveTo(x, y); ctx.lineTo(x + xoff, y + yoff); } ctx.stroke(); } // draw border if (bw) { // If either borderWidth or borderColor is an object, then draw the border // line by line instead of as one rectangle bc = options.grid.borderColor; if(typeof bw == "object" || typeof bc == "object") { if (typeof bw !== "object") { bw = {top: bw, right: bw, bottom: bw, left: bw}; } if (typeof bc !== "object") { bc = {top: bc, right: bc, bottom: bc, left: bc}; } if (bw.top > 0) { ctx.strokeStyle = bc.top; ctx.lineWidth = bw.top; ctx.beginPath(); ctx.moveTo(0 - bw.left, 0 - bw.top/2); ctx.lineTo(plotWidth, 0 - bw.top/2); ctx.stroke(); } if (bw.right > 0) { ctx.strokeStyle = bc.right; ctx.lineWidth = bw.right; ctx.beginPath(); ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); ctx.lineTo(plotWidth + bw.right / 2, plotHeight); ctx.stroke(); } if (bw.bottom > 0) { ctx.strokeStyle = bc.bottom; ctx.lineWidth = bw.bottom; ctx.beginPath(); ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); ctx.lineTo(0, plotHeight + bw.bottom / 2); ctx.stroke(); } if (bw.left > 0) { ctx.strokeStyle = bc.left; ctx.lineWidth = bw.left; ctx.beginPath(); ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); ctx.lineTo(0- bw.left/2, 0); ctx.stroke(); } } else { ctx.lineWidth = bw; ctx.strokeStyle = options.grid.borderColor; ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); } } ctx.restore(); } function drawAxisLabels() { $.each(allAxes(), function (_, axis) { var box = axis.box, legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, font = axis.options.font || "flot-tick-label tickLabel", tick, x, y, halign, valign; // Remove text before checking for axis.show and ticks.length; // otherwise plugins, like flot-tickrotor, that draw their own // tick labels will end up with both theirs and the defaults. surface.removeText(layer); if (!axis.show || axis.ticks.length == 0) return; for (var i = 0; i < axis.ticks.length; ++i) { tick = axis.ticks[i]; if (!tick.label || tick.v < axis.min || tick.v > axis.max) continue; if (axis.direction == "x") { halign = "center"; x = plotOffset.left + axis.p2c(tick.v); if (axis.position == "bottom") { y = box.top + box.padding; } else { y = box.top + box.height - box.padding; valign = "bottom"; } } else { valign = "middle"; y = plotOffset.top + axis.p2c(tick.v); if (axis.position == "left") { x = box.left + box.width - box.padding; halign = "right"; } else { x = box.left + box.padding; } } surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); } }); } function drawSeries(series) { if (series.lines.show) drawSeriesLines(series); if (series.bars.show) drawSeriesBars(series); if (series.points.show) drawSeriesPoints(series); } function drawSeriesLines(series) { function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize, prevx = null, prevy = null; ctx.beginPath(); for (var i = ps; i < points.length; i += ps) { var x1 = points[i - ps], y1 = points[i - ps + 1], x2 = points[i], y2 = points[i + 1]; if (x1 == null || x2 == null) continue; // clip with ymin if (y1 <= y2 && y1 < axisy.min) { if (y2 < axisy.min) continue; // line segment is outside // compute new intersection point x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.min; } else if (y2 <= y1 && y2 < axisy.min) { if (y1 < axisy.min) continue; x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.min; } // clip with ymax if (y1 >= y2 && y1 > axisy.max) { if (y2 > axisy.max) continue; x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.max; } else if (y2 >= y1 && y2 > axisy.max) { if (y1 > axisy.max) continue; x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.max; } // clip with xmin if (x1 <= x2 && x1 < axisx.min) { if (x2 < axisx.min) continue; y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.min; } else if (x2 <= x1 && x2 < axisx.min) { if (x1 < axisx.min) continue; y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.min; } // clip with xmax if (x1 >= x2 && x1 > axisx.max) { if (x2 > axisx.max) continue; y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.max; } else if (x2 >= x1 && x2 > axisx.max) { if (x1 > axisx.max) continue; y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.max; } if (x1 != prevx || y1 != prevy) ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); prevx = x2; prevy = y2; ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); } ctx.stroke(); } function plotLineArea(datapoints, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize, bottom = Math.min(Math.max(0, axisy.min), axisy.max), i = 0, top, areaOpen = false, ypos = 1, segmentStart = 0, segmentEnd = 0; // we process each segment in two turns, first forward // direction to sketch out top, then once we hit the // end we go backwards to sketch the bottom while (true) { if (ps > 0 && i > points.length + ps) break; i += ps; // ps is negative if going backwards var x1 = points[i - ps], y1 = points[i - ps + ypos], x2 = points[i], y2 = points[i + ypos]; if (areaOpen) { if (ps > 0 && x1 != null && x2 == null) { // at turning point segmentEnd = i; ps = -ps; ypos = 2; continue; } if (ps < 0 && i == segmentStart + ps) { // done with the reverse sweep ctx.fill(); areaOpen = false; ps = -ps; ypos = 1; i = segmentStart = segmentEnd + ps; continue; } } if (x1 == null || x2 == null) continue; // clip x values // clip with xmin if (x1 <= x2 && x1 < axisx.min) { if (x2 < axisx.min) continue; y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.min; } else if (x2 <= x1 && x2 < axisx.min) { if (x1 < axisx.min) continue; y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.min; } // clip with xmax if (x1 >= x2 && x1 > axisx.max) { if (x2 > axisx.max) continue; y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.max; } else if (x2 >= x1 && x2 > axisx.max) { if (x1 > axisx.max) continue; y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.max; } if (!areaOpen) { // open area ctx.beginPath(); ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); areaOpen = true; } // now first check the case where both is outside if (y1 >= axisy.max && y2 >= axisy.max) { ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); continue; } else if (y1 <= axisy.min && y2 <= axisy.min) { ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); continue; } // else it's a bit more complicated, there might // be a flat maxed out rectangle first, then a // triangular cutout or reverse; to find these // keep track of the current x values var x1old = x1, x2old = x2; // clip the y values, without shortcutting, we // go through all cases in turn // clip with ymin if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.min; } else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.min; } // clip with ymax if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.max; } else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.max; } // if the x value was changed we got a rectangle // to fill if (x1 != x1old) { ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); // it goes to (x1, y1), but we fill that below } // fill triangular section, this sometimes result // in redundant points if (x1, y1) hasn't changed // from previous line to, but we just ignore that ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); // fill the other rectangle if it's there if (x2 != x2old) { ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); } } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); ctx.lineJoin = "round"; var lw = series.lines.lineWidth, sw = series.shadowSize; // FIXME: consider another form of shadow when filling is turned on if (lw > 0 && sw > 0) { // draw shadow as a thick and thin line with transparency ctx.lineWidth = sw; ctx.strokeStyle = "rgba(0,0,0,0.1)"; // position shadow at angle from the mid of line var angle = Math.PI/18; plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); ctx.lineWidth = sw/2; plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); } ctx.lineWidth = lw; ctx.strokeStyle = series.color; var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); if (fillStyle) { ctx.fillStyle = fillStyle; plotLineArea(series.datapoints, series.xaxis, series.yaxis); } if (lw > 0) plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); ctx.restore(); } function drawSeriesPoints(series) { function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { var points = datapoints.points, ps = datapoints.pointsize; for (var i = 0; i < points.length; i += ps) { var x = points[i], y = points[i + 1]; if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) continue; ctx.beginPath(); x = axisx.p2c(x); y = axisy.p2c(y) + offset; if (symbol == "circle") ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); else symbol(ctx, x, y, radius, shadow); ctx.closePath(); if (fillStyle) { ctx.fillStyle = fillStyle; ctx.fill(); } ctx.stroke(); } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); var lw = series.points.lineWidth, sw = series.shadowSize, radius = series.points.radius, symbol = series.points.symbol; // If the user sets the line width to 0, we change it to a very // small value. A line width of 0 seems to force the default of 1. // Doing the conditional here allows the shadow setting to still be // optional even with a lineWidth of 0. if( lw == 0 ) lw = 0.0001; if (lw > 0 && sw > 0) { // draw shadow in two steps var w = sw / 2; ctx.lineWidth = w; ctx.strokeStyle = "rgba(0,0,0,0.1)"; plotPoints(series.datapoints, radius, null, w + w/2, true, series.xaxis, series.yaxis, symbol); ctx.strokeStyle = "rgba(0,0,0,0.2)"; plotPoints(series.datapoints, radius, null, w/2, true, series.xaxis, series.yaxis, symbol); } ctx.lineWidth = lw; ctx.strokeStyle = series.color; plotPoints(series.datapoints, radius, getFillStyle(series.points, series.color), 0, false, series.xaxis, series.yaxis, symbol); ctx.restore(); } function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { var left, right, bottom, top, drawLeft, drawRight, drawTop, drawBottom, tmp; // in horizontal mode, we start the bar from the left // instead of from the bottom so it appears to be // horizontal rather than vertical if (horizontal) { drawBottom = drawRight = drawTop = true; drawLeft = false; left = b; right = x; top = y + barLeft; bottom = y + barRight; // account for negative bars if (right < left) { tmp = right; right = left; left = tmp; drawLeft = true; drawRight = false; } } else { drawLeft = drawRight = drawTop = true; drawBottom = false; left = x + barLeft; right = x + barRight; bottom = b; top = y; // account for negative bars if (top < bottom) { tmp = top; top = bottom; bottom = tmp; drawBottom = true; drawTop = false; } } // clip if (right < axisx.min || left > axisx.max || top < axisy.min || bottom > axisy.max) return; if (left < axisx.min) { left = axisx.min; drawLeft = false; } if (right > axisx.max) { right = axisx.max; drawRight = false; } if (bottom < axisy.min) { bottom = axisy.min; drawBottom = false; } if (top > axisy.max) { top = axisy.max; drawTop = false; } left = axisx.p2c(left); bottom = axisy.p2c(bottom); right = axisx.p2c(right); top = axisy.p2c(top); // fill the bar if (fillStyleCallback) { c.fillStyle = fillStyleCallback(bottom, top); c.fillRect(left, top, right - left, bottom - top) } // draw outline if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { c.beginPath(); // FIXME: inline moveTo is buggy with excanvas c.moveTo(left, bottom); if (drawLeft) c.lineTo(left, top); else c.moveTo(left, top); if (drawTop) c.lineTo(right, top); else c.moveTo(right, top); if (drawRight) c.lineTo(right, bottom); else c.moveTo(right, bottom); if (drawBottom) c.lineTo(left, bottom); else c.moveTo(left, bottom); c.stroke(); } } function drawSeriesBars(series) { function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize; for (var i = 0; i < points.length; i += ps) { if (points[i] == null) continue; drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); // FIXME: figure out a way to add shadows (for instance along the right edge) ctx.lineWidth = series.bars.lineWidth; ctx.strokeStyle = series.color; var barLeft; switch (series.bars.align) { case "left": barLeft = 0; break; case "right": barLeft = -series.bars.barWidth; break; default: barLeft = -series.bars.barWidth / 2; } var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); ctx.restore(); } function getFillStyle(filloptions, seriesColor, bottom, top) { var fill = filloptions.fill; if (!fill) return null; if (filloptions.fillColor) return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); var c = $.color.parse(seriesColor); c.a = typeof fill == "number" ? fill : 0.4; c.normalize(); return c.toString(); } function insertLegend() { if (options.legend.container != null) { $(options.legend.container).html(""); } else { placeholder.find(".legend").remove(); } if (!options.legend.show) { return; } var fragments = [], entries = [], rowStarted = false, lf = options.legend.labelFormatter, s, label; // Build a list of legend entries, with each having a label and a color for (var i = 0; i < series.length; ++i) { s = series[i]; if (s.label) { label = lf ? lf(s.label, s) : s.label; if (label) { entries.push({ label: label, color: s.color }); } } } // Sort the legend using either the default or a custom comparator if (options.legend.sorted) { if ($.isFunction(options.legend.sorted)) { entries.sort(options.legend.sorted); } else if (options.legend.sorted == "reverse") { entries.reverse(); } else { var ascending = options.legend.sorted != "descending"; entries.sort(function(a, b) { return a.label == b.label ? 0 : ( (a.label < b.label) != ascending ? 1 : -1 // Logical XOR ); }); } } // Generate markup for the list of entries, in their final order for (var i = 0; i < entries.length; ++i) { var entry = entries[i]; if (i % options.legend.noColumns == 0) { if (rowStarted) fragments.push('</tr>'); fragments.push('<tr>'); rowStarted = true; } fragments.push( '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' + '<td class="legendLabel">' + entry.label + '</td>' ); } if (rowStarted) fragments.push('</tr>'); if (fragments.length == 0) return; var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>'; if (options.legend.container != null) $(options.legend.container).html(table); else { var pos = "", p = options.legend.position, m = options.legend.margin; if (m[0] == null) m = [m, m]; if (p.charAt(0) == "n") pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; else if (p.charAt(0) == "s") pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; if (p.charAt(1) == "e") pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; else if (p.charAt(1) == "w") pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder); if (options.legend.backgroundOpacity != 0.0) { // put in the transparent background // separately to avoid blended labels and // label boxes var c = options.legend.backgroundColor; if (c == null) { c = options.grid.backgroundColor; if (c && typeof c == "string") c = $.color.parse(c); else c = $.color.extract(legend, 'background-color'); c.a = 1; c = c.toString(); } var div = legend.children(); $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity); } } } // interactive features var highlights = [], redrawTimeout = null; // returns the data item the mouse is over, or null if none is found function findNearbyItem(mouseX, mouseY, seriesFilter) { var maxDistance = options.grid.mouseActiveRadius, smallestDistance = maxDistance * maxDistance + 1, item = null, foundPoint = false, i, j, ps; for (i = series.length - 1; i >= 0; --i) { if (!seriesFilter(series[i])) continue; var s = series[i], axisx = s.xaxis, axisy = s.yaxis, points = s.datapoints.points, mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster my = axisy.c2p(mouseY), maxx = maxDistance / axisx.scale, maxy = maxDistance / axisy.scale; ps = s.datapoints.pointsize; // with inverse transforms, we can't use the maxx/maxy // optimization, sadly if (axisx.options.inverseTransform) maxx = Number.MAX_VALUE; if (axisy.options.inverseTransform) maxy = Number.MAX_VALUE; if (s.lines.show || s.points.show) { for (j = 0; j < points.length; j += ps) { var x = points[j], y = points[j + 1]; if (x == null) continue; // For points and lines, the cursor must be within a // certain distance to the data point if (x - mx > maxx || x - mx < -maxx || y - my > maxy || y - my < -maxy) continue; // We have to calculate distances in pixels, not in // data units, because the scales of the axes may be different var dx = Math.abs(axisx.p2c(x) - mouseX), dy = Math.abs(axisy.p2c(y) - mouseY), dist = dx * dx + dy * dy; // we save the sqrt // use <= to ensure last point takes precedence // (last generally means on top of) if (dist < smallestDistance) { smallestDistance = dist; item = [i, j / ps]; } } } if (s.bars.show && !item) { // no other point can be nearby var barLeft, barRight; switch (s.bars.align) { case "left": barLeft = 0; break; case "right": barLeft = -s.bars.barWidth; break; default: barLeft = -s.bars.barWidth / 2; } barRight = barLeft + s.bars.barWidth; for (j = 0; j < points.length; j += ps) { var x = points[j], y = points[j + 1], b = points[j + 2]; if (x == null) continue; // for a bar graph, the cursor must be inside the bar if (series[i].bars.horizontal ? (mx <= Math.max(b, x) && mx >= Math.min(b, x) && my >= y + barLeft && my <= y + barRight) : (mx >= x + barLeft && mx <= x + barRight && my >= Math.min(b, y) && my <= Math.max(b, y))) item = [i, j / ps]; } } } if (item) { i = item[0]; j = item[1]; ps = series[i].datapoints.pointsize; return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), dataIndex: j, series: series[i], seriesIndex: i }; } return null; } function onMouseMove(e) { if (options.grid.hoverable) triggerClickHoverEvent("plothover", e, function (s) { return s["hoverable"] != false; }); } function onMouseLeave(e) { if (options.grid.hoverable) triggerClickHoverEvent("plothover", e, function (s) { return false; }); } function onClick(e) { triggerClickHoverEvent("plotclick", e, function (s) { return s["clickable"] != false; }); } // trigger click or hover event (they send the same parameters // so we share their code) function triggerClickHoverEvent(eventname, event, seriesFilter) { var offset = eventHolder.offset(), canvasX = event.pageX - offset.left - plotOffset.left, canvasY = event.pageY - offset.top - plotOffset.top, pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); pos.pageX = event.pageX; pos.pageY = event.pageY; var item = findNearbyItem(canvasX, canvasY, seriesFilter); if (item) { // fill in mouse pos for any listeners out there item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); } if (options.grid.autoHighlight) { // clear auto-highlights for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; if (h.auto == eventname && !(item && h.series == item.series && h.point[0] == item.datapoint[0] && h.point[1] == item.datapoint[1])) unhighlight(h.series, h.point); } if (item) highlight(item.series, item.datapoint, eventname); } placeholder.trigger(eventname, [ pos, item ]); } function triggerRedrawOverlay() { var t = options.interaction.redrawOverlayInterval; if (t == -1) { // skip event queue drawOverlay(); return; } if (!redrawTimeout) redrawTimeout = setTimeout(drawOverlay, t); } function drawOverlay() { redrawTimeout = null; // draw highlights octx.save(); overlay.clear(); octx.translate(plotOffset.left, plotOffset.top); var i, hi; for (i = 0; i < highlights.length; ++i) { hi = highlights[i]; if (hi.series.bars.show) drawBarHighlight(hi.series, hi.point); else drawPointHighlight(hi.series, hi.point); } octx.restore(); executeHooks(hooks.drawOverlay, [octx]); } function highlight(s, point, auto) { if (typeof s == "number") s = series[s]; if (typeof point == "number") { var ps = s.datapoints.pointsize; point = s.datapoints.points.slice(ps * point, ps * (point + 1)); } var i = indexOfHighlight(s, point); if (i == -1) { highlights.push({ series: s, point: point, auto: auto }); triggerRedrawOverlay(); } else if (!auto) highlights[i].auto = false; } function unhighlight(s, point) { if (s == null && point == null) { highlights = []; triggerRedrawOverlay(); return; } if (typeof s == "number") s = series[s]; if (typeof point == "number") { var ps = s.datapoints.pointsize; point = s.datapoints.points.slice(ps * point, ps * (point + 1)); } var i = indexOfHighlight(s, point); if (i != -1) { highlights.splice(i, 1); triggerRedrawOverlay(); } } function indexOfHighlight(s, p) { for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; if (h.series == s && h.point[0] == p[0] && h.point[1] == p[1]) return i; } return -1; } function drawPointHighlight(series, point) { var x = point[0], y = point[1], axisx = series.xaxis, axisy = series.yaxis, highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) return; var pointRadius = series.points.radius + series.points.lineWidth / 2; octx.lineWidth = pointRadius; octx.strokeStyle = highlightColor; var radius = 1.5 * pointRadius; x = axisx.p2c(x); y = axisy.p2c(y); octx.beginPath(); if (series.points.symbol == "circle") octx.arc(x, y, radius, 0, 2 * Math.PI, false); else series.points.symbol(octx, x, y, radius, false); octx.closePath(); octx.stroke(); } function drawBarHighlight(series, point) { var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), fillStyle = highlightColor, barLeft; switch (series.bars.align) { case "left": barLeft = 0; break; case "right": barLeft = -series.bars.barWidth; break; default: barLeft = -series.bars.barWidth / 2; } octx.lineWidth = series.bars.lineWidth; octx.strokeStyle = highlightColor; drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); } function getColorOrGradient(spec, bottom, top, defaultColor) { if (typeof spec == "string") return spec; else { // assume this is a gradient spec; IE currently only // supports a simple vertical gradient properly, so that's // what we support too var gradient = ctx.createLinearGradient(0, top, 0, bottom); for (var i = 0, l = spec.colors.length; i < l; ++i) { var c = spec.colors[i]; if (typeof c != "string") { var co = $.color.parse(defaultColor); if (c.brightness != null) co = co.scale('rgb', c.brightness); if (c.opacity != null) co.a *= c.opacity; c = co.toString(); } gradient.addColorStop(i / (l - 1), c); } return gradient; } } } // Add the plot function to the top level of the jQuery object $.plot = function(placeholder, data, options) { //var t0 = new Date(); var plot = new Plot($(placeholder), data, options, $.plot.plugins); //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); return plot; }; $.plot.version = "0.8.3"; $.plot.plugins = []; // Also add the plot function as a chainable property $.fn.plot = function(data, options) { return this.each(function() { $.plot(this, data, options); }); }; // round to nearby lower multiple of base function floorInBase(n, base) { return base * Math.floor(n / base); } })(jQuery); jquery.colorbox.js000060400000074420152526553050010262 0ustar00/*! Colorbox 1.6.4 license: MIT http://www.jacklmoore.com/colorbox */ (function ($, document, window) { var // Default settings object. // See http://jacklmoore.com/colorbox for details. defaults = { // data sources html: false, photo: false, iframe: false, inline: false, // behavior and appearance transition: "elastic", speed: 300, fadeOut: 300, width: false, initialWidth: "600", innerWidth: false, maxWidth: false, minWidth:false, height: false, initialHeight: "450", innerHeight: false, maxHeight: false, minHeight:false, scalePhotos: true, scrolling: true, opacity: 0.9, preloading: true, className: false, overlayClose: true, escKey: true, arrowKey: true, top: false, bottom: false, left: false, right: false, fixed: false, data: undefined, closeButton: true, fastIframe: true, open: false, reposition: true, loop: true, slideshow: false, slideshowAuto: true, slideshowSpeed: 2500, slideshowStart: "start slideshow", slideshowStop: "stop slideshow", photoRegex: /\.(gif|png|jp(e|g|eg)|bmp|ico|webp|jxr|svg)((#|\?).*)?$/i, // alternate image paths for high-res displays retinaImage: false, retinaUrl: false, retinaSuffix: '@2x.$1', // internationalization current: "image {current} of {total}", previous: "previous", next: "next", close: "close", xhrError: "This content failed to load.", imgError: "This image failed to load.", // accessbility returnFocus: true, trapFocus: true, // callbacks onOpen: false, onLoad: false, onComplete: false, onCleanup: false, onClosed: false, rel: function() { return this.rel; }, href: function() { // using this.href would give the absolute url, when the href may have been inteded as a selector (e.g. '#container') return $(this).attr('href'); }, title: function() { return this.title; }, createImg: function() { var img = new Image(); var attrs = $(this).data('cbox-img-attrs'); if (typeof attrs === 'object') { $.each(attrs, function(key, val){ img[key] = val; }); } return img; }, createIframe: function() { var iframe = document.createElement('iframe'); var attrs = $(this).data('cbox-iframe-attrs'); if (typeof attrs === 'object') { $.each(attrs, function(key, val){ iframe[key] = val; }); } if ('frameBorder' in iframe) { iframe.frameBorder = 0; } if ('allowTransparency' in iframe) { iframe.allowTransparency = "true"; } iframe.name = (new Date()).getTime(); // give the iframe a unique name to prevent caching iframe.allowFullscreen = true; return iframe; } }, // Abstracting the HTML and event identifiers for easy rebranding colorbox = 'colorbox', colorbox_class = 'box_mediabox box_modalbox', prefix = 'cbox', boxElement = prefix + 'Element', // Events event_open = prefix + '_open', event_load = prefix + '_load', event_complete = prefix + '_complete', event_cleanup = prefix + '_cleanup', event_closed = prefix + '_closed', event_purge = prefix + '_purge', // Cached jQuery Object Variables $overlay, $box, $wrap, $content, $topBorder, $leftBorder, $rightBorder, $bottomBorder, $related, $window, $loaded, $loadingBay, $loadingOverlay, $title, $current, $slideshow, $next, $prev, $close, $groupControls, $events = $('<a/>'), // $({}) would be prefered, but there is an issue with jQuery 1.4.2 // Variables for cached values or use across multiple functions settings, interfaceHeight, interfaceWidth, loadedHeight, loadedWidth, index, photo, open, active, closing, loadingTimer, publicMethod, div = "div", requests = 0, previousCSS = {}, init; // **************** // HELPER FUNCTIONS // **************** // Convenience function for creating new jQuery objects function $tag(tag, id, css) { var element = document.createElement(tag); if (id) { element.id = prefix + id; } if (css) { element.style.cssText = css; } return $(element); } // Get the window height using innerHeight when available to avoid an issue with iOS // http://bugs.jquery.com/ticket/6724 function winheight() { return window.innerHeight ? window.innerHeight : $(window).height(); } function Settings(element, options) { if (options !== Object(options)) { options = {}; } this.cache = {}; this.el = element; this.value = function(key) { var dataAttr; if (this.cache[key] === undefined) { dataAttr = $(this.el).attr('data-cbox-'+key); if (dataAttr !== undefined) { this.cache[key] = dataAttr; } else if (options[key] !== undefined) { this.cache[key] = options[key]; } else if (defaults[key] !== undefined) { this.cache[key] = defaults[key]; } } return this.cache[key]; }; this.get = function(key) { var value = this.value(key); return $.isFunction(value) ? value.call(this.el, this) : value; }; } // Determine the next and previous members in a group. function getIndex(increment) { var max = $related.length, newIndex = (index + increment) % max; return (newIndex < 0) ? max + newIndex : newIndex; } // Convert '%' and 'px' values to integers function setSize(size, dimension) { return Math.round((/%/.test(size) ? ((dimension === 'x' ? $window.width() : winheight()) / 100) : 1) * parseInt(size, 10)); } // Checks an href to see if it is a photo. // There is a force photo option (photo: true) for hrefs that cannot be matched by the regex. function isImage(settings, url) { return settings.get('photo') || settings.get('photoRegex').test(url); } function retinaUrl(settings, url) { return settings.get('retinaUrl') && window.devicePixelRatio > 1 ? url.replace(settings.get('photoRegex'), settings.get('retinaSuffix')) : url; } function trapFocus(e) { if ('contains' in $box[0] && !$box[0].contains(e.target) && e.target !== $overlay[0]) { e.stopPropagation(); $box.focus(); } } function setClass(str) { if (setClass.str !== str) { $box.add($overlay).removeClass(setClass.str).addClass(str); setClass.str = str; } } function getRelated(rel) { index = 0; if (rel && rel !== false && rel !== 'nofollow') { $related = $('.' + boxElement).filter(function () { var options = $.data(this, colorbox); var settings = new Settings(this, options); return (settings.get('rel') === rel); }); index = $related.index(settings.el); // Check direct calls to Colorbox. if (index === -1) { $related = $related.add(settings.el); index = $related.length - 1; } } else { $related = $(settings.el); } } function trigger(event) { // for external use $(document).trigger(event); // for internal use $events.triggerHandler(event); } var slideshow = (function(){ var active, className = prefix + "Slideshow_", click = "click." + prefix, timeOut; function clear () { clearTimeout(timeOut); } function set() { if (settings.get('loop') || $related[index + 1]) { clear(); timeOut = setTimeout(publicMethod.next, settings.get('slideshowSpeed')); } } function start() { $slideshow .html(settings.get('slideshowStop')) .unbind(click) .one(click, stop); $events .bind(event_complete, set) .bind(event_load, clear); $box.removeClass(className + "off").addClass(className + "on"); } function stop() { clear(); $events .unbind(event_complete, set) .unbind(event_load, clear); $slideshow .html(settings.get('slideshowStart')) .unbind(click) .one(click, function () { publicMethod.next(); start(); }); $box.removeClass(className + "on").addClass(className + "off"); } function reset() { active = false; $slideshow.hide(); clear(); $events .unbind(event_complete, set) .unbind(event_load, clear); $box.removeClass(className + "off " + className + "on"); } return function(){ if (active) { if (!settings.get('slideshow')) { $events.unbind(event_cleanup, reset); reset(); } } else { if (settings.get('slideshow') && $related[1]) { active = true; $events.one(event_cleanup, reset); if (settings.get('slideshowAuto')) { start(); } else { stop(); } $slideshow.show(); } } }; }()); function launch(element) { var options; if (!closing) { options = $(element).data(colorbox); settings = new Settings(element, options); getRelated(settings.get('rel')); if (!open) { open = active = true; // Prevents the page-change action from queuing up if the visitor holds down the left or right keys. setClass(settings.get('className')); // Show colorbox so the sizes can be calculated in older versions of jQuery $box.css({visibility:'hidden', display:'block', opacity:''}); $loaded = $tag(div, 'LoadedContent', 'width:0; height:0; overflow:hidden; visibility:hidden'); $content.css({width:'', height:''}).append($loaded); // Cache values needed for size calculations interfaceHeight = $topBorder.height() + $bottomBorder.height() + $content.outerHeight(true) - $content.height(); interfaceWidth = $leftBorder.width() + $rightBorder.width() + $content.outerWidth(true) - $content.width(); loadedHeight = $loaded.outerHeight(true); loadedWidth = $loaded.outerWidth(true); // Opens inital empty Colorbox prior to content being loaded. var initialWidth = setSize(settings.get('initialWidth'), 'x'); var initialHeight = setSize(settings.get('initialHeight'), 'y'); var maxWidth = settings.get('maxWidth'); var maxHeight = settings.get('maxHeight'); settings.w = Math.max((maxWidth !== false ? Math.min(initialWidth, setSize(maxWidth, 'x')) : initialWidth) - loadedWidth - interfaceWidth, 0); settings.h = Math.max((maxHeight !== false ? Math.min(initialHeight, setSize(maxHeight, 'y')) : initialHeight) - loadedHeight - interfaceHeight, 0); $loaded.css({width:'', height:settings.h}); publicMethod.position(); trigger(event_open); settings.get('onOpen'); $groupControls.add($title).hide(); $box.focus(); if (settings.get('trapFocus')) { // Confine focus to the modal // Uses event capturing that is not supported in IE8- if (document.addEventListener) { document.addEventListener('focus', trapFocus, true); $events.one(event_closed, function () { document.removeEventListener('focus', trapFocus, true); }); } } // Return focus on closing if (settings.get('returnFocus')) { $events.one(event_closed, function () { $(settings.el).focus(); }); } } var opacity = parseFloat(settings.get('opacity')); $overlay.css({ opacity: opacity === opacity ? opacity : '', cursor: settings.get('overlayClose') ? 'pointer' : '', visibility: 'visible' }).show(); if (settings.get('closeButton')) { $close.html(settings.get('close')).appendTo($content); } else { $close.appendTo('<div/>'); // replace with .detach() when dropping jQuery < 1.4 } load(); } } // Colorbox's markup needs to be added to the DOM prior to being called // so that the browser will go ahead and load the CSS background images. function appendHTML() { if (!$box) { init = false; $window = $(window); $box = $tag(div).attr({ id: colorbox, 'class': ($.support.opacity === false ? prefix + 'IE ' : '')+colorbox_class, // class for optional IE8 & lower targeted CSS. role: 'dialog', tabindex: '-1' }).hide(); $overlay = $tag(div, "Overlay").hide(); $loadingOverlay = $([$tag(div, "LoadingOverlay")[0],$tag(div, "LoadingGraphic")[0]]); $wrap = $tag(div, "Wrapper"); $content = $tag(div, "Content").append( $title = $tag(div, "Title"), $current = $tag(div, "Current"), $prev = $('<button type="button"/>').attr({id:prefix+'Previous'}), $next = $('<button type="button"/>').attr({id:prefix+'Next'}), $slideshow = $('<button type="button"/>').attr({id:prefix+'Slideshow'}), $loadingOverlay ); $close = $('<button type="button"/>').attr({id:prefix+'Close'}); $wrap.append( // The 3x3 Grid that makes up Colorbox $tag(div).append( $tag(div, "TopLeft"), $topBorder = $tag(div, "TopCenter"), $tag(div, "TopRight") ), $tag(div, false, 'clear:left').append( $leftBorder = $tag(div, "MiddleLeft"), $content, $rightBorder = $tag(div, "MiddleRight") ), $tag(div, false, 'clear:left').append( $tag(div, "BottomLeft"), $bottomBorder = $tag(div, "BottomCenter"), $tag(div, "BottomRight") ) ).find('div div').css({'float': 'left'}); $loadingBay = $tag(div, false, 'position:absolute; width:9999px; visibility:hidden; display:none; max-width:none;'); $groupControls = $next.add($prev).add($current).add($slideshow); } if (document.body && !$box.parent().length) { $(document.body).append($overlay, $box.append($wrap, $loadingBay)); } } // Add Colorbox's event bindings function addBindings() { function clickHandler(e) { // ignore non-left-mouse-clicks and clicks modified with ctrl / command, shift, or alt. // See: http://jacklmoore.com/notes/click-events/ if (!(e.which > 1 || e.shiftKey || e.altKey || e.metaKey || e.ctrlKey)) { e.preventDefault(); launch(this); } } if ($box) { if (!init) { init = true; // Anonymous functions here keep the public method from being cached, thereby allowing them to be redefined on the fly. $next.click(function () { publicMethod.next(); }); $prev.click(function () { publicMethod.prev(); }); $close.click(function () { publicMethod.close(); }); $overlay.click(function () { if (settings.get('overlayClose')) { publicMethod.close(); } }); // Key Bindings $(document).bind('keydown.' + prefix, function (e) { var key = e.keyCode; if (open && settings.get('escKey') && key === 27) { e.preventDefault(); publicMethod.close(); } if (open && settings.get('arrowKey') && $related[1] && !e.altKey) { if (key === 37) { e.preventDefault(); $prev.click(); } else if (key === 39) { e.preventDefault(); $next.click(); } } }); if ($.isFunction($.fn.on)) { // For jQuery 1.7+ $(document).on('click.'+prefix, '.'+boxElement, clickHandler); } else { // For jQuery 1.3.x -> 1.6.x // This code is never reached in jQuery 1.9, so do not contact me about 'live' being removed. // This is not here for jQuery 1.9, it's here for legacy users. $('.'+boxElement).live('click.'+prefix, clickHandler); } } return true; } return false; } // Don't do anything if Colorbox already exists. if ($[colorbox]) { return; } // Append the HTML when the DOM loads $(appendHTML); // **************** // PUBLIC FUNCTIONS // Usage format: $.colorbox.close(); // Usage from within an iframe: parent.jQuery.colorbox.close(); // **************** publicMethod = $.fn[colorbox] = $[colorbox] = function (options, callback) { var settings; var $obj = this; options = options || {}; if ($.isFunction($obj)) { // assume a call to $.colorbox $obj = $('<a/>'); options.open = true; } if (!$obj[0]) { // colorbox being applied to empty collection return $obj; } appendHTML(); if (addBindings()) { if (callback) { options.onComplete = callback; } $obj.each(function () { var old = $.data(this, colorbox) || {}; $.data(this, colorbox, $.extend(old, options)); var eltclass = $(this).attr('class'); if (eltclass){ if (eltclass.indexOf("boxWidth-")!== -1){ var w = eltclass.match(/boxWidth-([^\s'">]*)/); w = w[1].replace(/pc/, '%'); // % not allowed in html attribute ; use 100pc instead of 100% $.data(this, colorbox, $.extend($.data(this, colorbox), {width: w})); } if (eltclass.indexOf("boxHeight-")!== -1){ var h = eltclass.match(/boxHeight-([^\s'">]*)/); h = h[1].replace(/pc/, '%'); // % not allowed in html attribute ; use 100pc instead of 100% $.data(this, colorbox, $.extend($.data(this, colorbox), {height: h})); } if (eltclass.indexOf("boxIframe")!== -1){ $.data(this, colorbox, $.extend($.data(this, colorbox), {iframe: true})); } if (eltclass.indexOf("boxInline")!== -1) { $.data(this, colorbox, $.extend($.data(this, colorbox),{inline:true})); } if (eltclass.indexOf("boxSlideshow_off")!== -1){ $.data(this, colorbox, $.extend($.data(this, colorbox), {slideshow: false})); } } }).addClass(boxElement); settings = new Settings($obj[0], options); if (settings.get('open')) { launch($obj[0]); } } return $obj; }; publicMethod.position = function (speed, loadedCallback) { var css, top = 0, left = 0, offset = $box.offset(), scrollTop, scrollLeft; $window.unbind('resize.' + prefix); // remove the modal so that it doesn't influence the document width/height $box.css({top: -9e4, left: -9e4}); scrollTop = $window.scrollTop(); scrollLeft = $window.scrollLeft(); if (settings.get('fixed')) { offset.top -= scrollTop; offset.left -= scrollLeft; $box.css({position: 'fixed'}); } else { top = scrollTop; left = scrollLeft; $box.css({position: 'absolute'}); } // keeps the top and left positions within the browser's viewport. if (settings.get('right') !== false) { left += Math.max($window.width() - settings.w - loadedWidth - interfaceWidth - setSize(settings.get('right'), 'x'), 0); } else if (settings.get('left') !== false) { left += setSize(settings.get('left'), 'x'); } else { left += Math.round(Math.max($window.width() - settings.w - loadedWidth - interfaceWidth, 0) / 2); } if (settings.get('bottom') !== false) { top += Math.max(winheight() - settings.h - loadedHeight - interfaceHeight - setSize(settings.get('bottom'), 'y'), 0); } else if (settings.get('top') !== false) { top += setSize(settings.get('top'), 'y'); } else { top += Math.round(Math.max(winheight() - settings.h - loadedHeight - interfaceHeight, 0) / 2); } $box.css({top: offset.top, left: offset.left, visibility:'visible'}); // this gives the wrapper plenty of breathing room so it's floated contents can move around smoothly, // but it has to be shrank down around the size of div#colorbox when it's done. If not, // it can invoke an obscure IE bug when using iframes. $wrap[0].style.width = $wrap[0].style.height = "9999px"; function modalDimensions() { $topBorder[0].style.width = $bottomBorder[0].style.width = $content[0].style.width = (parseInt($box[0].style.width,10) - interfaceWidth)+'px'; $content[0].style.height = $leftBorder[0].style.height = $rightBorder[0].style.height = (parseInt($box[0].style.height,10) - interfaceHeight)+'px'; } css = {width: settings.w + loadedWidth + interfaceWidth, height: settings.h + loadedHeight + interfaceHeight, top: top, left: left}; // setting the speed to 0 if the content hasn't changed size or position if (speed) { var tempSpeed = 0; $.each(css, function(i){ if (css[i] !== previousCSS[i]) { tempSpeed = speed; return; } }); speed = tempSpeed; } previousCSS = css; if (!speed) { $box.css(css); } $box.dequeue().animate(css, { duration: speed || 0, complete: function () { modalDimensions(); active = false; // shrink the wrapper down to exactly the size of colorbox to avoid a bug in IE's iframe implementation. $wrap[0].style.width = (settings.w + loadedWidth + interfaceWidth) + "px"; $wrap[0].style.height = (settings.h + loadedHeight + interfaceHeight) + "px"; if (settings.get('reposition')) { setTimeout(function () { // small delay before binding onresize due to an IE8 bug. $window.bind('resize.' + prefix, publicMethod.position); }, 1); } if ($.isFunction(loadedCallback)) { loadedCallback(); } }, step: modalDimensions }); }; publicMethod.resize = function (options) { var scrolltop; if (open) { options = options || {}; if (options.width) { settings.w = setSize(options.width, 'x') - loadedWidth - interfaceWidth; } if (options.innerWidth) { settings.w = setSize(options.innerWidth, 'x'); } $loaded.css({width: settings.w}); if (options.height) { settings.h = setSize(options.height, 'y') - loadedHeight - interfaceHeight; } if (options.innerHeight) { settings.h = setSize(options.innerHeight, 'y'); } if (!options.innerHeight && !options.height) { scrolltop = $loaded.scrollTop(); $loaded.css({height: "auto"}); settings.h = $loaded.height(); } $loaded.css({height: settings.h}); if(scrolltop) { $loaded.scrollTop(scrolltop); } publicMethod.position(settings.get('transition') === "none" ? 0 : settings.get('speed')); } }; publicMethod.prep = function (object) { if (!open) { return; } var callback, speed = settings.get('transition') === "none" ? 0 : settings.get('speed'); $loaded.remove(); $loaded = $tag(div, 'LoadedContent').append(object); function getWidth() { settings.w = settings.w || $loaded.width(); settings.w = settings.minw && settings.minw > settings.w ? settings.minw : settings.w; settings.w = settings.mw && settings.mw < settings.w ? settings.mw : settings.w; return settings.w; } function getHeight() { settings.h = settings.h || $loaded.height(); settings.h = settings.minh && settings.minh > settings.h ? settings.minh : settings.h; settings.h = settings.mh && settings.mh < settings.h ? settings.mh : settings.h; return settings.h; } $loaded.hide() .appendTo($loadingBay.show())// content has to be appended to the DOM for accurate size calculations. .css({width: getWidth(), overflow: settings.get('scrolling') ? 'auto' : 'hidden'}) .css({height: getHeight()})// sets the height independently from the width in case the new width influences the value of height. .prependTo($content); $loadingBay.hide(); // floating the IMG removes the bottom line-height and fixed a problem where IE miscalculates the width of the parent element as 100% of the document width. $(photo).css({'float': 'none'}); setClass(settings.get('className')); callback = function () { var total = $related.length, iframe, complete; if (!open) { return; } function removeFilter() { // Needed for IE8 in versions of jQuery prior to 1.7.2 if ($.support.opacity === false) { $box[0].style.removeAttribute('filter'); } } complete = function () { clearTimeout(loadingTimer); $loadingOverlay.hide(); trigger(event_complete); settings.get('onComplete'); }; $title.html(settings.get('title')).show(); $loaded.show(); if (total > 1) { // handle grouping if (typeof settings.get('current') === "string") { $current.html(settings.get('current').replace('{current}', index + 1).replace('{total}', total)).show(); } $next[(settings.get('loop') || index < total - 1) ? "show" : "hide"]().html(settings.get('next')); $prev[(settings.get('loop') || index) ? "show" : "hide"]().html(settings.get('previous')); slideshow(); // Preloads images within a rel group if (settings.get('preloading')) { $.each([getIndex(-1), getIndex(1)], function(){ var img, i = $related[this], settings = new Settings(i, $.data(i, colorbox)), src = settings.get('href'); if (src && isImage(settings, src)) { src = retinaUrl(settings, src); img = document.createElement('img'); img.src = src; } }); } } else { $groupControls.hide(); } if (settings.get('iframe')) { iframe = settings.get('createIframe'); if (!settings.get('scrolling')) { iframe.scrolling = "no"; } $(iframe) .attr({ src: settings.get('href'), 'class': prefix + 'Iframe' }) .one('load', complete) .appendTo($loaded); $events.one(event_purge, function () { iframe.src = "//about:blank"; }); if (settings.get('fastIframe')) { $(iframe).trigger('load'); } } else { complete(); } if (settings.get('transition') === 'fade') { $box.fadeTo(speed, 1, removeFilter); } else { removeFilter(); } }; if (settings.get('transition') === 'fade') { $box.fadeTo(speed, 0, function () { publicMethod.position(0, callback); }); } else { publicMethod.position(speed, callback); } }; function load () { var href, setResize, prep = publicMethod.prep, $inline, request = ++requests; active = true; photo = false; trigger(event_purge); trigger(event_load); settings.get('onLoad'); settings.h = settings.get('height') ? setSize(settings.get('height'), 'y') - loadedHeight - interfaceHeight : settings.get('innerHeight') && setSize(settings.get('innerHeight'), 'y'); settings.w = settings.get('width') ? setSize(settings.get('width'), 'x') - loadedWidth - interfaceWidth : settings.get('innerWidth') && setSize(settings.get('innerWidth'), 'x'); // Sets the minimum dimensions for use in image scaling settings.mw = settings.w; settings.mh = settings.h; settings.minw = settings.w; settings.minh = settings.h; // Re-evaluate the minimum width and height based on maxWidth and maxHeight values. // If the width or height exceed the maxWidth or maxHeight, use the maximum values instead. if (settings.get('maxWidth')) { settings.mw = setSize(settings.get('maxWidth'), 'x') - loadedWidth - interfaceWidth; settings.mw = settings.w && settings.w < settings.mw ? settings.w : settings.mw; } if(settings.get('minWidth')){ settings.minw = setSize(settings.get('minWidth'), 'x') - loadedWidth - interfaceWidth; settings.minw = settings.w && settings.w > settings.minw ? settings.w : settings.minw; } if (settings.get('maxHeight')) { settings.mh = setSize(settings.get('maxHeight'), 'y') - loadedHeight - interfaceHeight; settings.mh = settings.h && settings.h < settings.mh ? settings.h : settings.mh; } if(settings.get('minHeight')){ settings.minh = setSize(settings.get('minHeight'), 'y') - loadedHeight - interfaceHeight; settings.minh = settings.h && settings.h > settings.minh ? settings.h : settings.minh; } href = settings.get('href'); loadingTimer = setTimeout(function () { $loadingOverlay.show(); }, 100); if (settings.get('inline')) { var $target = $(href).eq(0); // Inserts an empty placeholder where inline content is being pulled from. // An event is bound to put inline content back when Colorbox closes or loads new content. $inline = $('<div>').hide().insertBefore($target); $events.one(event_purge, function () { $inline.replaceWith($target); }); prep($target); } else if (settings.get('iframe')) { // IFrame element won't be added to the DOM until it is ready to be displayed, // to avoid problems with DOM-ready JS that might be trying to run in that iframe. prep(" "); } else if (settings.get('html')) { prep(settings.get('html')); } else if (isImage(settings, href)) { href = retinaUrl(settings, href); photo = settings.get('createImg'); $(photo) .addClass(prefix + 'Photo') .bind('error.'+prefix,function () { prep($tag(div, 'Error').html(settings.get('imgError'))); }) .one('load', function () { if (request !== requests) { return; } // A small pause because some browsers will occassionaly report a // img.width and img.height of zero immediately after the img.onload fires setTimeout(function(){ var percent; if (settings.get('retinaImage') && window.devicePixelRatio > 1) { photo.height = photo.height / window.devicePixelRatio; photo.width = photo.width / window.devicePixelRatio; } if (settings.get('scalePhotos')) { setResize = function () { photo.height -= photo.height * percent; photo.width -= photo.width * percent; }; if (settings.mw && photo.width > settings.mw) { percent = (photo.width - settings.mw) / photo.width; setResize(); } if (settings.mh && photo.height > settings.mh) { percent = (photo.height - settings.mh) / photo.height; setResize(); } } if (settings.h) { photo.style.marginTop = Math.max(settings.mh - photo.height, 0) / 2 + 'px'; } if ($related[1] && (settings.get('loop') || $related[index + 1])) { photo.style.cursor = 'pointer'; $(photo).bind('click.'+prefix, function () { publicMethod.next(); }); } photo.style.width = photo.width + 'px'; photo.style.height = photo.height + 'px'; prep(photo); }, 1); }); photo.src = href; } else if (href) { $loadingBay.load(href, settings.get('data'), function (data, status) { if (request === requests) { prep(status === 'error' ? $tag(div, 'Error').html(settings.get('xhrError')) : $(this).contents()); } }); } } // Navigates to the next page/image in a set. publicMethod.next = function () { if (!active && $related[1] && (settings.get('loop') || $related[index + 1])) { index = getIndex(1); launch($related[index]); } }; publicMethod.prev = function () { if (!active && $related[1] && (settings.get('loop') || index)) { index = getIndex(-1); launch($related[index]); } }; // Note: to use this within an iframe use the following format: parent.jQuery.colorbox.close(); publicMethod.close = function () { if (open && !closing) { closing = true; open = false; trigger(event_cleanup); settings.get('onCleanup'); $window.unbind('.' + prefix); $overlay.fadeTo(settings.get('fadeOut') || 0, 0); $box.stop().fadeTo(settings.get('fadeOut') || 0, 0, function () { $box.hide(); $overlay.hide(); trigger(event_purge); $loaded.remove(); setTimeout(function () { closing = false; trigger(event_closed); settings.get('onClosed'); }, 1); }); } }; // Removes changes Colorbox made to the document, but does not remove the plugin. publicMethod.remove = function () { if (!$box) { return; } $box.stop(); $[colorbox].close(); $box.stop(false, true).remove(); $overlay.remove(); closing = false; $box = null; $('.' + boxElement) .removeData(colorbox) .removeClass(boxElement); $(document).unbind('click.'+prefix).unbind('keydown.'+prefix); }; // A method for fetching the current element Colorbox is referencing. // returns a jQuery object. publicMethod.element = function () { return $(settings.el); }; publicMethod.settings = defaults; }(jQuery, document, window)); README000060400000037675152526553050005452 0ustar00DEMONSTRATION AND INSTRUCTIONS/API: ---------------------------------------------------------------------------------------------------------- http://colorpowered.com/colorbox/ HELP: ---------------------------------------------------------------------------------------------------------- Visit the ColorBox mailing list if you have any questions or problems. http://groups.google.com/group/colorbox/topics RELEASE NOTES: ---------------------------------------------------------------------------------------------------------- Version 1.3.6 - Jan 13 2010 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js * Small change to make Colorbox compatible with jQuery 1.4 Version 1.3.5 - December 15 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js * Fixed a bug introduced in 1.3.4 with IE7's display of example 2 and example 3, and auto-width in Opera. * Fixed a bug introduced in 1.3.4 where colorbox could not be launched by triggering an element's click event through JavaScript. * Minor refinements. Version 1.3.4 - December 5 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js * Event delegation is now used for elements that ColorBox is assigned to, rather than individual click events. * Additional callbacks have been added to represent other stages of ColorBox's lifecycle. Available callbacks, in order of their execution: onOpen, onLoad, onComplete, onCleanup, onClosed These take place at the same time as the event hooks, but will be better suited than the hooks for targeting specific instances of ColorBox. * Ajax content is now immediately added to the DOM to be more compatible if that content contains script tags. * Focus is now returned to the calling element on closing. * Fixed a bug where maxHeight and maxWidth did not work for non-photo content. * Direct calls no longer need 'open:true', it is assumed. Example: $.fn.colorbox({html:"<p>Hi</p>"}); Version 1.3.3 - November 7 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js * Changed $.fn.colorbox.element() to return a jQuery object rather the DOM element. * jQuery.colorbox-min.js is compressed with Google's Closure Compiler rather than YUI Compressor. Version 1.3.2 - October 27 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js * Added 'innerWidth' and 'innerHeight' options to allow people to easily set the size dimensions for ColorBox, without having to anticipate the size of the borders and buttons. * Renamed 'scrollbars' option to 'scrolling' to be in keeping with the existing HTML attribute. The option now also applies to iframes. * Bug fix: In Safari, positioning occassionally incorrect when using '100%' dimensions. * Bug fix: In IE6, the background overlay is briefly not full size when first viewing. * Bug fix: In Firefox, opening ColorBox causes a split second shift with a small minority of webpage layouts. * Simplified code in a few areas. Version 1.3.1 - September 16 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js/colorbox.css/colorbox-ie.css(removed) * Removed the IE-only stylesheets and conditional comments for example styles 1 & 4. All CSS is handled by a single CSS file for all examples. * Removed user-agent sniffing from the js and replaced it with feature detection. This will allow correct rendering for visitors masking their agent type. Version 1.3.0 - September 15 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js/colorbox.css * Added $.fn.colorbox.resize() method to allow ColorBox to resize it's height if it's contents change. * Added 'scrollbars' option to allow users to turn off scrollbars when using the resize() method. * Renamed the 'resize' option to be less ambiguous. It's now 'scalePhotos'. * Renamed the 'cbox_close' event to be less ambiguous. It's now 'cbox_cleanup'. It is the first thing to happen in the close method while the 'cbox_closed' event is the last to happen. * Fixed a bug with the slideshow mouseover graphics that appeared after ColorBox is opened a 2nd time. * Fixed a bug where ClearType may not work in IE6&7 if using the fade transition. * Minor code optimizations to increase compression. Version 1.2.9 - August 7 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js * Minor change to enable use with $.getScript(); * Minor change to the timing of the 'cbox_load' event so that it is more useful. * Added a direct link to a YouTube video to the examples. Version 1.2.8 - August 5 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js * Fixed a bug with the overlay in IE6 * Fixed a bug where left & right keypress events might be prematurely unbound. Version 1.2.7 - July 31 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js, example stylesheets and background images (core styles have not changed and the updates will not affect existing user themes / old example themes) * Code cleanup and reduction, better organization and documentation in the full source. * Added ability to use functions in place of static values for ColorBox's options (thanks Ken!). * Added an option for straight HTML. Example: $.fn.colorbox({html:'<p>Howdy</p>', open:true}) * Added an event for the beginning of the closing process. This is in addition to the event that already existed for when ColorBox had completely closed. 'cbox_close' and 'cbox_closed' respectively. * Fixed a minor bug in IE6 that would cause a brief content shift in the parent document when opening ColorBox. * Fixed a minor bug in IE6 that would reveal select elements that had a hidden visibility after closing ColorBox. * The 'esc' key is unbound now when ColorBox is not open, to avoid any potential conflicts. * Used background sprites for examples 1 & 4. Put IE-only (non-sprite) background images in a separate folder. * Example themes 1, 3, & 4 received slight visual tweaks. * Optimized pngs for smaller file size. * Added slices, grid, and correct sizing to the Adobe Illustrator file, all theme files are now export ready! Version 1.2.6 - July 15 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js * Fixed a bug with fixed width/height images in Opera 9.64. * Fixed a bug with trying to set a value for rel during a direct call to ColorBox. Example: $.fn.colorbox({rel:'foo', open:true}); * Changed how href/rel/title settings are determined to avoid users having to manually update ColorBox settings if they use JavaScript to update any of those attributes, after ColorBox has been defined. * Fixed a FF3 bug where the back button was disabled after closing an iframe. Version 1.2.5 - June 23 2009 Files Changed: jquery.colorbox.js/jquery.colorbox-min.js * Changed the point at which iframe srcs are set (to eliminate the need to refresh the iframe once it has been added to the DOM). * Removed unnecessary return values for a very slight code reduction. Version 1.2.4 - June 9 2009 Files Changed: jquery.colorbox.js, jquery.colorbox-min.js * Fixed an issue where ColorBox may not close completely if it is closed during a transition animation. * Minor code reduction. Version 1.2.3 - June 4 2009 * Fixed a png transparency stacking issue in IE. * More accurate Ajax auto-sizing if the user was depending on the #cboxLoadedContent ID for CSS styling. * Added a public function for returning the current html element that ColorBox is associated with. Example use: var that = $.fn.colorbox.element(); * Added bicubic scaling for resized images in the original IE7. * Removed the IE6 stylesheet and png files from Example 3. It now uses the same png file for the controls that the rest of the browsers use (an alpha transparency PNG8). This example now only has 2 graphics files and 1 stylesheet. Version 1.2.2 - May 28 2009 * Fixed an issue with the 'resize' option. Version 1.2.1 - May 28 2009 * Note: If you are upgrading, update your jquery.colorbox.js and colorbox.css files. * Added photo resizing. * Added a maximum width and maximum height. Example: {height:800, maxHeight:'100%'}, would allow the box to be a maximum potential height of 800px, instead of a fixed height of 800px. With maxHeight of 100% the height of ColorBox cannot exceed the height of the browser window. * Added 'rel' setting to add the ability to set an alternative rel for any ColorBox call. This allows the user to group any combination of elements together for a gallery, or to override an existing rel. attribute so those element are not grouped together, without having to alter their rel in the HTML. * Added a 'photo' setting to force ColorBox to display a link as a photo. Use this when automatic photo detection fails (such as using a url like 'photo.php' instead of 'photo.jpg', 'photo.jpg#1', or 'photo.jpg?pic=1') * Removed the need to ever create disposable elements to call colorbox on. ColorBox can now be called directly, without being associated with any existing element, by using the following format: $.fn.colorbox({open:true, href:'yourLink.xxx'}); * ColorBox settings are now persistent and unique for each element. This allows for extremely flexible options for individual elements. You could use this to create a gallery in which each page in the gallery has different settings. One could be a photo with a fade transition, next could be an inline element with an elastic transition with a set width and height, etc. * For user callbacks, 'this' now refers to the element colorbox was opened from. * Fixed a minor grouping issue with IE6, when transition type is set to 'none'. * Added an Adobe Illustrator file that contains the borders and buttons used in the various examples. Version 1.2 - May 13 2009 * Note: If you are upgrading from a previous version of ColorBox, see the UPGRADE file. * Added a slideshow feature. * Added re-positioning on browser resize. If the browser is resized, ColorBox will recenter itself onscreen. * Added hooks for key events: cbox_open, cbox_load, cbox_complete, cbox_closed. Example of using the hooks to build a primitive sideshow: $().bind('cbox_complete', function(){ setTimeout($.fn.colorbox.next, 1500); }); * Fixed an IE transparency-stacking problem, where transparent PNGs would show through to the background overlay. * Fixed an IE iframe issue where the ifame might shift up and to the left under certain circumstances. * Fixed an IE6 bug where the loading overlay was not at full height. * Removed the delay in switching between same-sized gallery content when using transitions. * Changed how iframes are loaded to make it more compatible with iframed pages that use DOM dependent JavaScript. * Changed how the JS is structured to be better organized and increase compression. Increased documentation. * Changed CSS :hover states to a .hover class. This sidesteps a minor IE8 bug with css hover states and allows easier access to hover state user styles from the JavaScript. * Changed: elements added to the DOM have new ID's. The naming is more consistent and less likely to cause conflicts with existing website stylesheets. All stylesheets have been updated. * Changed the behavior for prev/next links so that ColorBox does not get hung up on broken links. A visitor can now skip through broken or long-loading links by clicking prev/next buttons. * Changed the naming of variables in the parameter map to be more concise and intuitive. * Removed colorbox.css. Combined the colorbox.css styles with jquery.colorbox.js: the css file was not large enough to warrant being a separate file. Version 1.1.6 - April 28 2009 * Prevented the default action of the next & previous anchors and the left and right keys for gallery mode. * Fixed a bug where the title element was being added back to the DOM when closing ColorBox while using inline content. * Fixed a bug where IE7 would crash for example 2. * Smaller filesize: removed a small amount of unused code and rewrote the HTML injection with less syntax. * Added a public method for closing ColorBox: $.fn.colorbox.close(). This will allow iframe users to add an event to close ColorBox without having to create an additional function. Version 1.1.5 - April 11 2009 * Fixed minor issues with exiting ColorBox. Version 1.1.4 - April 08 2009 * Fixed a bug in the fade transition where ColorBox not close completely if instructed to close during the fade-in portion of the transition. Version 1.1.3 - April 06 2009 * Fixed an IE6&7 issue with using ColorBox to display animated GIFs. Version 1.1.2 - April 05 2009 * Added ability to change content when ColorBox is already open. * Added vertical photo centering now works for all browsers (this feature previously excluded IE6&7). * Added namespacing to the esc-key keydown event for people who want to disable it: "keydown.colorClose" * Added 'title' setting to add the ability to set an alternative title for any ColorBox call. * Fixed rollover navigation issue with IE8. (Added JS-based rollover state due to a browser-bug.) * Fixed an overflow issue for when the fixed width/height is smaller than the size of a photo. * Fixed a bug in the fade transition where the border would still come up if ColorBox was closed mid-transition. * Switch from JSMin to Yui Compressor for minification. Minified code now under 7KB. Version 1.1.1 - March 31 2009 * More robust image detection regex. Now detects image file types with url fragments and/or query strings. * Added 'nofollow' exception to rel grouping. * Changed how images are loaded into the DOM to prevent premature size calculation by ColorBox. * Added timestamp to iframe name to prevent caching - this was a problem in some browsers if the user had multiple iframes and the visitor left the page and came back, or if they refreshed the page. Version 1.1.0 - March 21 2009 Major Changes: * Animation is now much smoother and less resource intensive. * Added support for % sizing. * Callback option added. * Inline content now preserves JavaScript events, and changes made while ColorBox is open are also preserved. * Added 'href' setting to add the ability to set an alternative href for any anchor, or to assign the ColorBox event to non-anchors. Example: $('button').colorbox({'href':'process.php'}) Example: $('a[href='http://msn.com']).colorbox({'href':'http://google.com', iframe:true}); Minor Changes: * Photos are now horizontally centered if they are smaller than the lightbox size. Also vertically centered for browsers newer than IE7. * Buttons in the examples are now included in the 'protected zone'. The lightbox will never expand it's borders or buttons beyond an accessible area of the screen. * Keypress events don't queue up by holding down the arrow keys. * Added option to close ColorBox by clicking on the background overlay. * Added 'none' transition setting. * Changed 'contentIframe' and 'contentInline' to 'inline' and 'iframe'. Removed 'contentAjax' because it is automatically assumed for non-image file types. * Changed 'contentWidth' and 'contentHeight' to 'fixedWidth' and 'fixedHeight'. These sizes now reflect the total size of the lightbox, not just the inner content. This is so users can accurately anticipate % sizes without fear of creating scrollbars. * Clicking on a photo will now switch to the next photo in a set. * Loading.gif is more stable in it's position. * Added a minified version. * Code passes JSLint. Version 1.0.5 - March 11 2009 * Redo: Fixed a bug where IE would cut off the bottom portion of a photo, if the photo was larger than the document dimensions. Version 1.0.4 - March 10 2009 * Added an option to allow users to automatically open the lightbox. Example usage: $(".colorbox").colorbox({open:true}); * Fixed a bug where IE would cut off the bottom portion of a photo, if the photo was larger than the document dimensions. Version 1.0.3 - March 09 2009 * Fixed vertical centering for Safari 3.0.x. Version 1.0.2 - March 06 2009 * Corrected a typo. * Changed the content-type check so that it does not assume all links to photos should actually display photos. This allows for Ajax/inline/and iframe calls on anchors linking to picture file types. Version 1.0.1 - March 05 2009 * Fixed keydown events (esc, left arrow, right arrow) for Webkit browsers. Version 1.0 - March 03 2009 * First release splash.mediabox.js000060400000001341152526553050010166 0ustar00jQuery(function($){ // poser le cookie au callback de la box chargee var set_cookie = function() { Cookies.set("modalsplash", box_settings.splash_url, { expires: 7 }); } $.modalboxsplash = function(href, options) { if (box_settings_iframe) { $.fn.mediabox($.extend({ href:href, onComplete:set_cookie, overlayClose:true, iframe: true, width: box_settings_splash_width, height: box_settings_splash_height },options)); } else { $.fn.mediabox($.extend({ href:href, onComplete:set_cookie, overlayClose:true },options)); } }; // ouvrir la splash page si pas deja vue if (Cookies.get("modalsplash") != box_settings.splash_url) { $.modalboxsplash(box_settings.splash_url); } });spip.mediabox.js000060400000007702152526553050007656 0ustar00// Inside the function "this" will be "document" when called by ready() // and "the ajaxed element" when called because of onAjaxLoad var mediaboxInit = function() { //console.log(box_settings); var options = { transition:box_settings.trans, speed:box_settings.speed, maxWidth:box_settings.maxW, maxHeight:box_settings.maxH, minWidth:box_settings.minW, minHeight:box_settings.minH, opacity:box_settings.opa, slideshowSpeed:box_settings.ssSpeed, slideshowStart:box_settings.str_ssStart, slideshowStop:box_settings.str_ssStop, current:box_settings.str_cur, previous:box_settings.str_prev, next:box_settings.str_next, close:box_settings.str_close, splash_url:box_settings.splash_url }; // passer le portfolio de la dist en mode galerie if (box_settings.sel_g){ jQuery(box_settings.sel_g, this).not('.hasbox,#colorbox') .attr("onclick","") // se debarrasser du onclick de SPIP .colorbox(jQuery.extend({}, options, {rel:'galerieauto',slideshow:true,slideshowAuto:false})) .addClass("hasbox"); } if (box_settings.tt_img) { // selectionner tous les liens vers des images jQuery("a[type=\'image/jpeg\'],a[type=\'image/png\'],a[type=\'image/gif\']", this).not('.hasbox') .attr("onclick","") // se debarrasser du onclick de SPIP .colorbox(options) // activer la box .addClass("hasbox") // noter qu\'on l\'a vue ; } // charger la box sur autre chose if (box_settings.sel_c){ jQuery(box_settings.sel_c).not('.hasbox') .colorbox(jQuery.extend({}, options, {slideshow:true,slideshowAuto:false})) .addClass("hasbox") // noter qu\'on l\'a vue ; } }; /* initialiser maintenant si box_settings est deja la * nb en cas de defer sur le chargement du scipt javascript principal */ if (typeof(box_settings)!='undefined') (function($){ if(typeof onAjaxLoad == "function") onAjaxLoad(mediaboxInit); $(mediaboxInit); })(jQuery); ;(function ($) { /* * overlayClose: (Boolean:false) Allow click on overlay to close the dialog? * iframe: (Boolean:false) Open box in iframe * minHeight: (Number:200) The minimum height for the container * minWidth: (Number:200) The minimum width for the container * maxHeight: (Number:null) The maximum height for the container. If not specified, the window height is used. * maxWidth: (Number:null) The maximum width for the container. If not specified, the window width is used. * autoResize: (Boolean:false) Resize container on window resize? Use with caution - this may have undesirable side-effects. * onOpen: (Function:null) The callback function used in place of SimpleModal's open * onShow: (Function:null) The callback function used after the modal dialog has opened * onClose: (Function:null) The callback function used in place of SimpleModal's close */ $.fn.mediabox = function (options) { var cbox_options = { overlayClose: true, iframe: false, transition:box_settings.trans, speed:box_settings.speed, maxWidth:box_settings.maxW, maxHeight:box_settings.maxH, minWidth:box_settings.minW, minHeight:box_settings.minH, opacity:box_settings.opa, slideshowStart:box_settings.str_ssStart, slideshowStop:box_settings.str_ssStop, current:box_settings.str_cur, previous:box_settings.str_prev, next:box_settings.str_next, close:box_settings.str_close, onOpen: (options && options.onOpen) || null, onComplete: (options && options.onShow) || null, onClosed: (options && options.onClose) || null }; if (this === jQuery.fn) return $.colorbox($.extend(cbox_options,options)); else return this.colorbox($.extend(cbox_options,options)); }; $.mediaboxClose = function () {$.fn.colorbox.close();}; // API modalbox $.mediabox = function(options) { return jQuery.fn.mediabox(options);} $.modalbox = function (href, options) {$.fn.mediabox($.extend({href:href,inline:href.match(/^#/)?true:false,overlayClose:true},options));}; $.modalboxload = function (url, options) { $.modalbox(url,options); }; $.modalboxclose = $.mediaboxClose; })(jQuery);spiptree.js000060400000016614152526734320006752 0ustar00;(function($){ $.fn.spiptree = function(options) { var $mytree = $(this); var $mytree_source = $mytree.clone(); // $mytree.after($mytree_source); options.plugins = [ "types", "search", "state" ]; if (options.drag) { options.plugins.push("dnd"); } options.types = { "#" : { "valid_children" : ["default"] }, "default" : { "icon" : options.default.icon, "valid_children" : [ "default" ] } } options.confirm = { move: null }; $.each(options.objets, function(nom, desc) { options.types.default.valid_children.push(desc.type); options.types.default.valid_children.push("box_" + desc.type); options.types[desc.type] = { "icon" : desc.icon, "max_children" : 0, "max_depth" : 0 }; options.types["box_" + desc.type] = { "icon" : desc.icon, "max_depth" : 1, "valid_children" : [ desc.type ] }; }); $mytree.jstree({ "plugins" : options.plugins, "core" : { "animation" : 0, "check_callback" : function (op, node, par, pos, more) { if (op === "move_node") { // à la fin d'un déplacement // (pas de trig spécifique au début d'un déplacement :( ) if (more && more.core) { // ne pas tenter de déplacer une box non chargée var box = (node.type.substring(0, 4) == 'box_'); if (box && (!node.children || !node.children.length)) { $('#contenu p.success, #contenu div.error').remove(); $("#contenu #mytree_actions").after("<div class='error removable' onClick='$(this).remove();'><p /><ul class='spip' /></div>"); $box = $("#contenu div.error"); $box.find('p').text(options.textes.deplacement.vide + ' ' + options.textes.deplacement.suggerer_deplier); return false; } // Demander 1 fois (et 1 seule) une confirmation, même si on déplace 5 items d'un coup if (options.confirm.move === null) { options.confirm.move = confirm( options.textes.deplacement.confirmation ); // enlever les messages de réussite ou d'erreur pour en avoir des tout neufs if (options.confirm.move) { $('#contenu p.success, #contenu div.error').remove(); } } return options.confirm.move; } else { // redemander la confirmation au prochain tour options.confirm.move = null; } } return true; }, "data" : function (node, cb) { // on est obligé de tout charger en ajax (même la racine) // donc on charge 1 fois la racine avec le html d'origine if (node.id === '#') { cb($mytree_source.html()); } // et pour ce qu'on ne connait pas (classe css 'jstree-closed' sur un LI, et pas de UL à l'intérieur) // on fait un appel ajax pour obtenir la liste correspondant à l'objet souhaité, lorsque c'est demandé. else { var objet = node.data.objet; var id_rubrique = (objet == 'rubrique') ? node.id.split('-')[1] : node.parent.split('-')[1]; var params = { "id_rubrique": id_rubrique, "objet": objet }; if (options.statut) { params.statut = options.statut; } $.ajax({ url: options.urls.plan, data: params, dataType: 'html', cache: false, }).done(function(data) { if (data !== undefined) { cb(data); } else { cb(""); } }); } } }, "search" : { "show_only_matches" : true, }, "types" : options.types }); if (options.drag) { $mytree.addClass('drag'); } // un clic d'une feuille amène sur son lien // mais… éviter que le plugin 'state' clique automatiquement lorsqu'il restaure // la sélection précédente ! $mytree.one("state_ready.jstree", function () { $(this).on("changed.jstree", function (e, data) { data.instance.save_state(); var node = data.instance.get_node(data.node, true); if (node) { var lien = node.children('a').attr('href'); if (lien && !(options.drag && $(data.event.target).hasClass('jstree-icon'))) { location.href = lien; } } }); }); var recharge_plan = false; // lorsqu'on déplace un nœud $mytree.on("move_node.jstree", function(event, data) { // si les parents sont identiques : pas de changement, // on ne peut/veut pas gérer ici les positionnements if (data.old_parent == data.parent) { // data.instance.refresh(); return true; } // il existe 2 cas de boites : // - un item (rubrique, article, site) a été déplacé // - un conteneur (box_xx) a été déplacé (ie: tous les articles qu'il contient par exemple) // dans ce cas on retrouve tous les identifiants déplacés var box = (data.node.type.substring(0, 4) == 'box_'); var infos = data.node.id.split('-'); // articles-rubrique-30 (box) ou article-30 (item) if (box) { var ids = []; $.each(data.node.children, function(key, val) { ids.push( val.split('-')[1] ); }); var params = { objet: infos[0], id_objet: ids, id_rubrique_source: infos[2], id_rubrique_destination: data.parent.split('-')[1] } } else if (infos[0] == 'rubrique') { // les rubriques n'ont pas de 'box_' et sont directement dans les sous rubriques var params = { objet: infos[0], id_objet: [ infos[1] ], id_rubrique_source: (data.old_parent == '#' ? 0 : data.old_parent.split('-')[1]), id_rubrique_destination: (data.parent == '#' ? 0 : data.parent.split('-')[1]) } } else { // un item, sa destination est soit une box (de même type) soit une rubrique var dest = data.parent.split('-'); // articles-rubrique-30 (box) ou rubrique-30 var params = { objet: infos[0], id_objet: [ infos[1] ], id_rubrique_source: data.old_parent.split('-')[2], id_rubrique_destination: (dest.length == 3 ? dest[2] : dest[1]), } } $mytree.animateLoading(); $.ajax({ url: options.urls.deplacer, data: params, dataType: 'json', cache: false, }).done(function(response) { if (response) { var nb_success = Object.keys(response.success).length; var nb_errors = Object.keys(response.errors).length; if (nb_success) { var $box = $("#contenu p.success"); if (!$box.length) { $("#contenu #mytree_actions").after("<p class='success removable' onClick='$(this).remove();'></p>"); $box = $("#contenu p.success").data('nb', 0); } nb = nb_success + $box.data('nb'); $box.data('nb', nb).text(nb == 1 ? options.textes.deplacement.reussi : options.textes.deplacement.reussis.replace('@nb@', nb)); } if (nb_errors) { var $box = $("#contenu div.error"); if (!$box.length) { $("#contenu #mytree_actions").after("<div class='error removable' onClick='$(this).remove();'><p /><ul class='spip' /></div>"); $box = $("#contenu div.error").data('nb', 0); } nb = nb_errors + $box.data('nb'); $box.data('nb', nb).find('p').text(nb == 1 ? options.textes.deplacement.echec : options.textes.deplacement.echecs.replace('@nb@', nb)); $.each(response.errors, function(i, error) { $box.find('ul').append("<li>[ " + i + "] " + error+ "</li>"); }); } $mytree.endLoading(); } if (recharge_plan) { clearTimeout(recharge_plan); } recharge_plan = setTimeout(function () { ajaxReload('plan'); }, 500); }); return true; }); // recherche automatique $mytree_search = $("#mytree_search"); var to = false; $mytree_search.keyup(function () { if (to) { clearTimeout(to); } to = setTimeout(function () { var v = $mytree_search.val(); $mytree.jstree(true).search(v); }, 250); }); }; })(jQuery);
/home/histoio/www/f41a3/../plugins-dist/../prive/../35802/javascript.tar