diff options
Diffstat (limited to 'module')
-rw-r--r-- | module/database/DatabaseBackend.py | 1 | ||||
-rw-r--r-- | module/database/FileDatabase.py | 5 | ||||
-rw-r--r-- | module/datatypes/PyPackage.py | 9 | ||||
-rw-r--r-- | module/remote/pyload.thrift | 21 | ||||
-rw-r--r-- | module/remote/ttypes.py | 11 | ||||
-rw-r--r-- | module/web/static/css/default/style.less | 65 | ||||
-rw-r--r-- | module/web/static/js/default.js | 15 | ||||
-rw-r--r-- | module/web/static/js/libs/jquery.flot-1.1.js | 2599 | ||||
-rw-r--r-- | module/web/static/js/libs/jquery.flot.min.js | 6 | ||||
-rw-r--r-- | module/web/static/js/libs/lodash-0.7.0.js (renamed from module/web/static/js/libs/lodash-0.5.2.js) | 1893 | ||||
-rw-r--r-- | module/web/static/js/views/packageView.js | 36 | ||||
-rw-r--r-- | module/web/templates/default/dashboard.html | 25 |
12 files changed, 3667 insertions, 1019 deletions
diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 58e1e74d8..b22f8ffc5 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -244,6 +244,7 @@ class DatabaseBackend(Thread): '"password" TEXT DEFAULT "" NOT NULL, ' '"added" INTEGER DEFAULT 0 NOT NULL,' # set by trigger '"status" INTEGER DEFAULT 0 NOT NULL,' + '"tags" TEXT DEFAULT "" NOT NULL,' '"packageorder" INTEGER DEFAULT -1 NOT NULL,' #incremented by trigger '"root" INTEGER DEFAULT -1 NOT NULL, ' '"owner" INTEGER NOT NULL, ' diff --git a/module/database/FileDatabase.py b/module/database/FileDatabase.py index 410f1088e..e9ef99cbe 100644 --- a/module/database/FileDatabase.py +++ b/module/database/FileDatabase.py @@ -180,8 +180,9 @@ class FileMethods(DatabaseMethods): data = OrderedDict() for r in self.c: data[r[0]] = PackageInfo( - r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], stats.get(r[0], zero_stats) + r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], None, r[9], r[10], stats.get(r[0], zero_stats) ) + # TODO: tags return data @@ -249,7 +250,7 @@ class FileMethods(DatabaseMethods): return None else: return PackageInfo( - r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], stats.get(r[0], zero_stats) if stats else None + r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], None, r[9], r[10], stats.get(r[0], zero_stats) if stats else None ) @async diff --git a/module/datatypes/PyPackage.py b/module/datatypes/PyPackage.py index 654a36f76..4118af190 100644 --- a/module/datatypes/PyPackage.py +++ b/module/datatypes/PyPackage.py @@ -29,9 +29,9 @@ class PyPackage: @staticmethod def fromInfoData(m, info): return PyPackage(m, info.pid, info.name, info.folder, info.root, info.owner, - info.site, info.comment, info.password, info.added, info.status, info.packageorder) + info.site, info.comment, info.password, info.added, info.tags, info.status, info.packageorder) - def __init__(self, manager, pid, name, folder, root, owner, site, comment, password, added, status, packageorder): + def __init__(self, manager, pid, name, folder, root, owner, site, comment, password, added, tags, status, packageorder): self.m = manager self.pid = pid @@ -43,6 +43,7 @@ class PyPackage: self.comment = comment self.password = password self.added = added + self.tags = tags self.status = status self.packageorder = packageorder self.timestamp = time() @@ -60,7 +61,7 @@ class PyPackage: def toInfoData(self): return PackageInfo(self.pid, self.name, self.folder, self.root, self.ownerid, self.site, - self.comment, self.password, self.added, self.status, self.packageorder + self.comment, self.password, self.added, self.tags, self.status, self.packageorder ) def getChildren(self): @@ -96,7 +97,7 @@ class PyPackage: class RootPackage(PyPackage): def __init__(self, m, owner): - PyPackage.__init__(self, m, -1, "root", "", owner, -2, "", "", "", 0, PackageStatus.Ok, 0) + PyPackage.__init__(self, m, -1, "root", "", owner, -2, "", "", "", 0, [], PackageStatus.Ok, 0) def getPath(self, name=""): return join(self.m.core.config["general"]["download_folder"], name) diff --git a/module/remote/pyload.thrift b/module/remote/pyload.thrift index 8257107b4..3b0f74cbc 100644 --- a/module/remote/pyload.thrift +++ b/module/remote/pyload.thrift @@ -32,6 +32,16 @@ enum DownloadStatus { Unknown } +// Download states, combination of several downloadstatuses +// defined in Filedatabase +enum DownloadStates { + All, + Finished, + Unfinished, + Failed, + Unmanaged // internal state +} + enum MediaType { All = 0 Other = 1, @@ -166,11 +176,12 @@ struct PackageInfo { 7: string comment, 8: string password, 9: UTCDate added, - 10: PackageStatus status, - 11: i16 packageorder, - 12: PackageStats stats, - 13: list<FileID> fids, - 14: list<PackageID> pids, + 10: list<string> tags, + 11: PackageStatus status, + 12: i16 packageorder, + 13: PackageStats stats, + 14: list<FileID> fids, + 15: list<PackageID> pids, } // thrift does not allow recursive datatypes, so all data is accumulated and mapped with id diff --git a/module/remote/ttypes.py b/module/remote/ttypes.py index 06f051fc7..70105275d 100644 --- a/module/remote/ttypes.py +++ b/module/remote/ttypes.py @@ -6,6 +6,12 @@ class BaseObject(object): __slots__ = [] +class DownloadStates: + Failed = 2 + Finished = 0 + Unfinished = 1 + Unmanaged = 3 + class DownloadStatus: Aborted = 12 Custom = 15 @@ -229,9 +235,9 @@ class PackageDoesNotExists(Exception): self.pid = pid class PackageInfo(BaseObject): - __slots__ = ['pid', 'name', 'folder', 'root', 'owner', 'site', 'comment', 'password', 'added', 'status', 'packageorder', 'stats', 'fids', 'pids'] + __slots__ = ['pid', 'name', 'folder', 'root', 'owner', 'site', 'comment', 'password', 'added', 'tags', 'status', 'packageorder', 'stats', 'fids', 'pids'] - def __init__(self, pid=None, name=None, folder=None, root=None, owner=None, site=None, comment=None, password=None, added=None, status=None, packageorder=None, stats=None, fids=None, pids=None): + def __init__(self, pid=None, name=None, folder=None, root=None, owner=None, site=None, comment=None, password=None, added=None, tags=None, status=None, packageorder=None, stats=None, fids=None, pids=None): self.pid = pid self.name = name self.folder = folder @@ -241,6 +247,7 @@ class PackageInfo(BaseObject): self.comment = comment self.password = password self.added = added + self.tags = tags self.status = status self.packageorder = packageorder self.stats = stats diff --git a/module/web/static/css/default/style.less b/module/web/static/css/default/style.less index a22f29999..180c763a2 100644 --- a/module/web/static/css/default/style.less +++ b/module/web/static/css/default/style.less @@ -1,19 +1,48 @@ /*
- General
+ Definitions
*/
@min-width: 1000px;
@header-height: 70px;
@footer-height: 100px;
-@margin-side: 150px;
+@margin-side: 100px;
@dark: #333333;
+@light: #ffffff;
@grey: #757575;
@yellow: #fee247;
@blue: #3a79aa;
+@lightblue: lighten(spin(@blue, 5), 10%);
+@darkblue: darken(spin(@blue, -5), 10%);
@emph: #FF7637;
+/*
+ Mixins
+*/
+
+
+.gradient (@origin: left, @start: #ffffff, @stop: #000000) {
+ background-color: @start;
+ background-image: -webkit-linear-gradient(@origin, @start, @stop);
+ background-image: -moz-linear-gradient(@origin, @start, @stop);
+ background-image: -o-linear-gradient(@origin, @start, @stop);
+ background-image: -ms-linear-gradient(@origin, @start, @stop);
+ background-image: linear-gradient(@origin, @start, @stop);
+}
+
+.transition (@prop: all, @time: 1s, @ease: linear) {
+ -webkit-transition: @prop @time @ease;
+ -moz-transition: @prop @time @ease;
+ -o-transition: @prop @time @ease;
+ -ms-transition: @prop @time @ease;
+ transition: @prop @time @ease;
+}
+
+
+/*
+ General
+ */
* {
margin: 0;
@@ -320,4 +349,36 @@ footer h2 { #dash-nav .dropdown-menu i {
margin-top: 4px;
padding-right: 5px;
+}
+
+
+#dashboard ul {
+ margin: 0;
+ list-style: none;
+}
+
+.package-view {
+ height: 30px;
+ width: 100%;
+ color: @light;
+ .gradient(top, @blue, @darkblue);
+// background-color: @blue;
+ font-weight: bold;
+ border-radius: 5px;
+ margin-bottom: 3px;
+}
+
+.package-view:hover {
+ .gradient(top, @blue, @lightblue);
+}
+
+
+.package-view a {
+ color: @light;
+}
+
+.package-graph {
+ display: inline;
+ width: 20px;
+ height: 20px;
}
\ No newline at end of file diff --git a/module/web/static/js/default.js b/module/web/static/js/default.js index e200f470a..cb2ef38c3 100644 --- a/module/web/static/js/default.js +++ b/module/web/static/js/default.js @@ -7,15 +7,18 @@ require.config({ jquery:"libs/jquery-1.8.0", jqueryui:"libs/jqueryui", - flot:"libs/jquery.flot.min", + flot:"libs/jquery.flot-1.1", + flotpie: "libs/jquery.flot.pie", transit:"libs/jquery.transit-0.1.3", omniwindow: "libs/jquery.omniwindow", bootstrap: "libs/bootstrap-2.1.1", - underscore:"libs/lodash-0.5.2", + underscore:"libs/lodash-0.7.0", backbone:"libs/backbone-0.9.2", +// handlebars: "libs/Handlebars-1.0rc1", - // Require.js Plugins + // Plugins +// hbs: "plugins/hbs-2.0.1", text:"plugins/text-2.0.3", tpl: "../../templates" @@ -29,11 +32,17 @@ require.config({ exports:"Backbone" //attaches "Backbone" to the window object }, "flot" : ["jquery"], + "flotpie" : ["flot"], "transit" : ["jquery"], "omniwindow" : ["jquery"], "bootstrap" : ["jquery"] } // end Shim Configuration + // Handlebar Configuration +// hbs : { +// templateExtension : 'hbs', +// disableI18n : true +// } }); define('default', ['jquery', 'backbone', 'routers/defaultRouter', 'views/headerView', 'views/packageTreeView', diff --git a/module/web/static/js/libs/jquery.flot-1.1.js b/module/web/static/js/libs/jquery.flot-1.1.js new file mode 100644 index 000000000..aabc544e9 --- /dev/null +++ b/module/web/static/js/libs/jquery.flot-1.1.js @@ -0,0 +1,2599 @@ +/*! Javascript plotting library for jQuery, v. 0.7. + * + * Released under the MIT license by IOLA, December 2007. + * + */ + +// first an inline dependency, jquery.colorhelpers.js, we inline it here +// for convenience + +/* Plugin for jQuery for working with colors. + * + * Version 1.1. + * + * Inspiration from jQuery color animation plugin by John Resig. + * + * Released under the MIT license by Ole Laursen, October 2009. + * + * Examples: + * + * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() + * var c = $.color.extract($("#mydiv"), 'background-color'); + * console.log(c.r, c.g, c.b, c.a); + * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" + * + * Note that .scale() and .add() return the same modified object + * instead of making a new one. + * + * V. 1.1: Fix error handling so e.g. parsing an empty string does + * produce a color rather than just crashing. + */ +(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); + +// the actual Flot code +(function($) { + function Plot(placeholder, data_, options_, plugins) { + // data is on the form: + // [ series1, series2 ... ] + // where series is either just the data as [ [x1, y1], [x2, y2], ... ] + // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } + + var series = [], + options = { + // the color theme used for graphs + colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], + legend: { + show: true, + noColumns: 1, // number of colums in legend table + labelFormatter: null, // fn: string -> string + labelBoxBorderColor: "#ccc", // border color for the little label boxes + container: null, // container (as jQuery object) to put legend in, null means default on top of graph + position: "ne", // position of default legend container within plot + margin: 5, // distance from grid edge to default legend container within plot + backgroundColor: null, // null means auto-detect + backgroundOpacity: 0.85 // set to 0 to avoid background + }, + xaxis: { + show: null, // null = auto-detect, true = always, false = never + position: "bottom", // or "top" + mode: null, // null or "time" + color: null, // base color, labels, ticks + tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" + transform: null, // null or f: number -> number to transform axis + inverseTransform: null, // if transform is set, this should be the inverse function + min: null, // min. value to show, null means set automatically + max: null, // max. value to show, null means set automatically + autoscaleMargin: null, // margin in % to add if auto-setting min/max + ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks + tickFormatter: null, // fn: number -> string + labelWidth: null, // size of tick labels in pixels + labelHeight: null, + reserveSpace: null, // whether to reserve space even if axis isn't shown + tickLength: null, // size in pixels of ticks, or "full" for whole line + alignTicksWithAxis: null, // axis number or null for no sync + + // mode specific options + tickDecimals: null, // no. of decimals, null means auto + tickSize: null, // number or [number, "unit"] + minTickSize: null, // number or [number, "unit"] + monthNames: null, // list of names of months + timeformat: null, // format string to use + twelveHourClock: false // 12 or 24 time in time mode + }, + yaxis: { + autoscaleMargin: 0.02, + position: "left" // or "right" + }, + xaxes: [], + yaxes: [], + series: { + points: { + show: false, + radius: 3, + lineWidth: 2, // in pixels + fill: true, + fillColor: "#ffffff", + symbol: "circle" // or callback + }, + lines: { + // we don't put in show: false so we can see + // whether lines were actively disabled + lineWidth: 2, // in pixels + fill: false, + fillColor: null, + steps: false + }, + bars: { + show: false, + lineWidth: 2, // in pixels + barWidth: 1, // in units of the x axis + fill: true, + fillColor: null, + align: "left", // or "center" + horizontal: false + }, + shadowSize: 3 + }, + grid: { + show: true, + aboveData: false, + color: "#545454", // primary color used for outline and labels + backgroundColor: null, // null for transparent, else color + borderColor: null, // set if different from the grid color + tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" + labelMargin: 5, // in pixels + axisMargin: 8, // in pixels + borderWidth: 2, // in pixels + minBorderMargin: null, // in pixels, null means taken from points radius + markings: null, // array of ranges or fn: axes -> array of ranges + markingsColor: "#f4f4f4", + markingsLineWidth: 2, + // interactive stuff + clickable: false, + hoverable: false, + autoHighlight: true, // highlight in case mouse is near + mouseActiveRadius: 10 // how far the mouse can be away to activate an item + }, + hooks: {} + }, + canvas = null, // the canvas for the plot itself + overlay = null, // canvas for interactive stuff on top of plot + eventHolder = null, // jQuery object that events should be bound to + ctx = null, octx = null, + xaxes = [], yaxes = [], + plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, + canvasWidth = 0, canvasHeight = 0, + plotWidth = 0, plotHeight = 0, + hooks = { + processOptions: [], + processRawData: [], + processDatapoints: [], + drawSeries: [], + draw: [], + bindEvents: [], + drawOverlay: [], + shutdown: [] + }, + plot = this; + + // public functions + plot.setData = setData; + plot.setupGrid = setupGrid; + plot.draw = draw; + plot.getPlaceholder = function() { return placeholder; }; + plot.getCanvas = function() { return canvas; }; + plot.getPlotOffset = function() { return plotOffset; }; + plot.width = function () { return plotWidth; }; + plot.height = function () { return plotHeight; }; + plot.offset = function () { + var o = eventHolder.offset(); + o.left += plotOffset.left; + o.top += plotOffset.top; + return o; + }; + plot.getData = function () { return series; }; + plot.getAxes = function () { + var res = {}, i; + $.each(xaxes.concat(yaxes), function (_, axis) { + if (axis) + res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; + }); + return res; + }; + plot.getXAxes = function () { return xaxes; }; + plot.getYAxes = function () { return yaxes; }; + plot.c2p = canvasToAxisCoords; + plot.p2c = axisToCanvasCoords; + plot.getOptions = function () { return options; }; + plot.highlight = highlight; + plot.unhighlight = unhighlight; + plot.triggerRedrawOverlay = triggerRedrawOverlay; + plot.pointOffset = function(point) { + return { + left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left), + top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top) + }; + }; + plot.shutdown = shutdown; + plot.resize = function () { + getCanvasDimensions(); + resizeCanvas(canvas); + resizeCanvas(overlay); + }; + + // public attributes + plot.hooks = hooks; + + // initialize + initPlugins(plot); + parseOptions(options_); + setupCanvases(); + setData(data_); + setupGrid(); + draw(); + bindEvents(); + + + function executeHooks(hook, args) { + args = [plot].concat(args); + for (var i = 0; i < hook.length; ++i) + hook[i].apply(this, args); + } + + function initPlugins() { + for (var i = 0; i < plugins.length; ++i) { + var p = plugins[i]; + p.init(plot); + if (p.options) + $.extend(true, options, p.options); + } + } + + function parseOptions(opts) { + var i; + + $.extend(true, options, opts); + + if (options.xaxis.color == null) + options.xaxis.color = options.grid.color; + if (options.yaxis.color == null) + options.yaxis.color = options.grid.color; + + if (options.xaxis.tickColor == null) // backwards-compatibility + options.xaxis.tickColor = options.grid.tickColor; + if (options.yaxis.tickColor == null) // backwards-compatibility + options.yaxis.tickColor = options.grid.tickColor; + + if (options.grid.borderColor == null) + options.grid.borderColor = options.grid.color; + if (options.grid.tickColor == null) + options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + + // fill in defaults in axes, copy at least always the + // first as the rest of the code assumes it'll be there + for (i = 0; i < Math.max(1, options.xaxes.length); ++i) + options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]); + for (i = 0; i < Math.max(1, options.yaxes.length); ++i) + options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]); + + // backwards compatibility, to be removed in future + if (options.xaxis.noTicks && options.xaxis.ticks == null) + options.xaxis.ticks = options.xaxis.noTicks; + if (options.yaxis.noTicks && options.yaxis.ticks == null) + options.yaxis.ticks = options.yaxis.noTicks; + if (options.x2axis) { + options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); + options.xaxes[1].position = "top"; + } + if (options.y2axis) { + options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); + options.yaxes[1].position = "right"; + } + if (options.grid.coloredAreas) + options.grid.markings = options.grid.coloredAreas; + if (options.grid.coloredAreasColor) + options.grid.markingsColor = options.grid.coloredAreasColor; + if (options.lines) + $.extend(true, options.series.lines, options.lines); + if (options.points) + $.extend(true, options.series.points, options.points); + if (options.bars) + $.extend(true, options.series.bars, options.bars); + if (options.shadowSize != null) + options.series.shadowSize = options.shadowSize; + + // save options on axes for future reference + for (i = 0; i < options.xaxes.length; ++i) + getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; + for (i = 0; i < options.yaxes.length; ++i) + getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; + + // add hooks from options + for (var n in hooks) + if (options.hooks[n] && options.hooks[n].length) + hooks[n] = hooks[n].concat(options.hooks[n]); + + executeHooks(hooks.processOptions, [options]); + } + + function setData(d) { + series = parseData(d); + fillInSeriesOptions(); + processData(); + } + + function parseData(d) { + var res = []; + for (var i = 0; i < d.length; ++i) { + var s = $.extend(true, {}, options.series); + + if (d[i].data != null) { + s.data = d[i].data; // move the data instead of deep-copy + delete d[i].data; + + $.extend(true, s, d[i]); + + d[i].data = s.data; + } + else + s.data = d[i]; + res.push(s); + } + + return res; + } + + function axisNumber(obj, coord) { + var a = obj[coord + "axis"]; + if (typeof a == "object") // if we got a real axis, extract number + a = a.n; + if (typeof a != "number") + a = 1; // default to first axis + return a; + } + + function allAxes() { + // return flat array without annoying null entries + return $.grep(xaxes.concat(yaxes), function (a) { return a; }); + } + + function canvasToAxisCoords(pos) { + // 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 i; + + // collect what we already got of colors + var neededColors = series.length, + usedColors = [], + assignedColors = []; + for (i = 0; i < series.length; ++i) { + var sc = series[i].color; + if (sc != null) { + --neededColors; + if (typeof sc == "number") + assignedColors.push(sc); + else + usedColors.push($.color.parse(series[i].color)); + } + } + + // we might need to generate more colors if higher indices + // are assigned + for (i = 0; i < assignedColors.length; ++i) { + neededColors = Math.max(neededColors, assignedColors[i] + 1); + } + + // produce colors as needed + var colors = [], variation = 0; + i = 0; + while (colors.length < neededColors) { + var c; + if (options.colors.length == i) // check degenerate case + c = $.color.make(100, 100, 100); + else + c = $.color.parse(options.colors[i]); + + // vary color if needed + var sign = variation % 2 == 1 ? -1 : 1; + c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2) + + // FIXME: if we're getting to close to something else, + // we should probably skip this one + colors.push(c); + + ++i; + if (i >= options.colors.length) { + i = 0; + ++variation; + } + } + + // fill in the options + 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; + } + + // 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; + + 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]; + + var 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)) { + format.push({ y: true, number: true, required: false, defaultValue: 0 }); + 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; + + 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.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; + + 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 || 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 = s.bars.align == "left" ? 0 : -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 makeCanvas(skipPositioning, cls) { + var c = document.createElement('canvas'); + c.className = cls; + c.width = canvasWidth; + c.height = canvasHeight; + + if (!skipPositioning) + $(c).css({ position: 'absolute', left: 0, top: 0 }); + + $(c).appendTo(placeholder); + + if (!c.getContext) // excanvas hack + c = window.G_vmlCanvasManager.initElement(c); + + // used for resetting in case we get replotted + c.getContext("2d").save(); + + return c; + } + + function getCanvasDimensions() { + canvasWidth = placeholder.width(); + canvasHeight = placeholder.height(); + + if (canvasWidth <= 0 || canvasHeight <= 0) + throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; + } + + function resizeCanvas(c) { + // resizing should reset the state (excanvas seems to be + // buggy though) + if (c.width != canvasWidth) + c.width = canvasWidth; + + if (c.height != canvasHeight) + c.height = canvasHeight; + + // so try to get back to the initial state (even if it's + // gone now, this should be safe according to the spec) + var cctx = c.getContext("2d"); + cctx.restore(); + + // and save again + cctx.save(); + } + + function setupCanvases() { + var reused, + existingCanvas = placeholder.children("canvas.base"), + existingOverlay = placeholder.children("canvas.overlay"); + + if (existingCanvas.length == 0 || existingOverlay == 0) { + // init everything + + placeholder.html(""); // make sure placeholder is clear + + placeholder.css({ padding: 0 }); // padding messes up the positioning + + if (placeholder.css("position") == 'static') + placeholder.css("position", "relative"); // for positioning labels and overlay + + getCanvasDimensions(); + + canvas = makeCanvas(true, "base"); + overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features + + reused = false; + } + else { + // reuse existing elements + + canvas = existingCanvas.get(0); + overlay = existingOverlay.get(0); + + reused = true; + } + + ctx = canvas.getContext("2d"); + octx = overlay.getContext("2d"); + + // we include the canvas in the event holder too, because IE 7 + // sometimes has trouble with the stacking order + eventHolder = $([overlay, canvas]); + + if (reused) { + // run shutdown in the old plot object + placeholder.data("plot").shutdown(); + + // reset reused canvases + plot.resize(); + + // make sure overlay pixels are cleared (canvas is cleared when we redraw) + octx.clearRect(0, 0, canvasWidth, canvasHeight); + + // then whack any remaining obvious garbage left + eventHolder.unbind(); + placeholder.children().not([canvas, overlay]).remove(); + } + + // save in case we get replotted + placeholder.data("plot", plot); + } + + function bindEvents() { + // bind events + if (options.grid.hoverable) { + eventHolder.mousemove(onMouseMove); + eventHolder.mouseleave(onMouseLeave); + } + + if (options.grid.clickable) + eventHolder.click(onClick); + + executeHooks(hooks.bindEvents, [eventHolder]); + } + + function shutdown() { + if (redrawTimeout) + clearTimeout(redrawTimeout); + + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mouseleave", onMouseLeave); + eventHolder.unbind("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, i, ticks = axis.ticks || [], labels = [], + l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv; + + function makeDummyDiv(labels, width) { + return $('<div style="position:absolute;top:-10000px;' + width + 'font-size:smaller">' + + '<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis">' + + labels.join("") + '</div></div>') + .appendTo(placeholder); + } + + if (axis.direction == "x") { + // to avoid measuring the widths of the labels (it's slow), we + // construct fixed-size boxes and put the labels inside + // them, we don't need the exact figures and the + // fixed-size box content is easy to center + if (w == null) + w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1)); + + // measure x label heights + if (h == null) { + labels = []; + for (i = 0; i < ticks.length; ++i) { + l = ticks[i].label; + if (l) + labels.push('<div class="tickLabel" style="float:left;width:' + w + 'px">' + l + '</div>'); + } + + if (labels.length > 0) { + // stick them all in the same div and measure + // collective height + labels.push('<div style="clear:left"></div>'); + dummyDiv = makeDummyDiv(labels, "width:10000px;"); + h = dummyDiv.height(); + dummyDiv.remove(); + } + } + } + else if (w == null || h == null) { + // calculate y label dimensions + for (i = 0; i < ticks.length; ++i) { + l = ticks[i].label; + if (l) + labels.push('<div class="tickLabel">' + l + '</div>'); + } + + if (labels.length > 0) { + dummyDiv = makeDummyDiv(labels, ""); + if (w == null) + w = dummyDiv.children().width(); + if (h == null) + h = dummyDiv.find("div.tickLabel").height(); + dummyDiv.remove(); + } + } + + if (w == null) + w = 0; + if (h == null) + h = 0; + + axis.labelWidth = w; + axis.labelHeight = h; + } + + function allocateAxisBoxFirstPhase(axis) { + // find the bounding box of the axis by looking at label + // widths/heights and ticks, make room by diminishing the + // plotOffset + + var lw = axis.labelWidth, + lh = axis.labelHeight, + pos = axis.options.position, + tickLength = axis.options.tickLength, + axismargin = options.grid.axisMargin, + padding = options.grid.labelMargin, + all = axis.direction == "x" ? xaxes : yaxes, + index; + + // determine axis margin + var samePosition = $.grep(all, function (a) { + return a && a.options.position == pos && a.reserveSpace; + }); + if ($.inArray(axis, samePosition) == samePosition.length - 1) + axismargin = 0; // outermost + + // determine tick length - if we're innermost, we can use "full" + if (tickLength == null) + tickLength = "full"; + + var sameDirection = $.grep(all, function (a) { + return a && a.reserveSpace; + }); + + var innermost = $.inArray(axis, sameDirection) == 0; + if (!innermost && tickLength == "full") + tickLength = 5; + + if (!isNaN(+tickLength)) + padding += +tickLength; + + // compute box + if (axis.direction == "x") { + lh += padding; + + if (pos == "bottom") { + plotOffset.bottom += lh + axismargin; + axis.box = { top: canvasHeight - 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: canvasWidth - plotOffset.right, width: lw }; + } + } + + // save for future reference + axis.position = pos; + axis.tickLength = tickLength; + axis.box.padding = padding; + axis.innermost = innermost; + } + + function allocateAxisBoxSecondPhase(axis) { + // set remaining bounding box coordinates + if (axis.direction == "x") { + axis.box.left = plotOffset.left; + axis.box.width = plotWidth; + } + else { + axis.box.top = plotOffset.top; + axis.box.height = plotHeight; + } + } + + function setupGrid() { + var i, axes = allAxes(); + + // first calculate the plot and axis box dimensions + + $.each(axes, function (_, axis) { + axis.show = axis.options.show; + if (axis.show == null) + axis.show = axis.used; // by default an axis is visible if it's got data + + axis.reserveSpace = axis.show || axis.options.reserveSpace; + + setRange(axis); + }); + + allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); + + plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0; + if (options.grid.show) { + $.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 in house, we can compute the + // axis 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 + var minMargin = options.grid.minBorderMargin; + if (minMargin == null) { + minMargin = 0; + for (i = 0; i < series.length; ++i) + minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2); + } + + for (var a in plotOffset) { + plotOffset[a] += options.grid.borderWidth; + plotOffset[a] = Math.max(minMargin, plotOffset[a]); + } + } + + plotWidth = canvasWidth - plotOffset.left - plotOffset.right; + plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; + + // now we got the proper plotWidth/Height, we can compute the scaling + $.each(axes, function (_, axis) { + setTransformationHelpers(axis); + }); + + if (options.grid.show) { + $.each(allocatedAxes, function (_, axis) { + allocateAxisBoxSecondPhase(axis); + }); + + insertAxisLabels(); + } + + 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" ? canvasWidth : canvasHeight); + + var delta = (axis.max - axis.min) / noTicks, + size, generator, unit, formatter, i, magn, norm; + + if (opts.mode == "time") { + // pretty handling of time + + // 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, + "year": 365.2425 * 24 * 60 * 60 * 1000 + }; + + + // the allowed tick sizes, after 1 year we use + // an integer algorithm + var spec = [ + [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"], [3, "month"], [6, "month"], + [1, "year"] + ]; + + var minSize = 0; + 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 (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; + size = spec[i][0]; + unit = spec[i][1]; + + // special-case the possibility of several years + if (unit == "year") { + magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); + norm = (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; + } + + axis.tickSize = opts.tickSize || [size, unit]; + + generator = function(axis) { + var ticks = [], + tickSize = axis.tickSize[0], unit = axis.tickSize[1], + d = new Date(axis.min); + + var step = tickSize * timeUnitSize[unit]; + + if (unit == "second") + d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); + if (unit == "minute") + d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); + if (unit == "hour") + d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); + if (unit == "month") + d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); + if (unit == "year") + d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); + + // reset smaller components + d.setUTCMilliseconds(0); + if (step >= timeUnitSize.minute) + d.setUTCSeconds(0); + if (step >= timeUnitSize.hour) + d.setUTCMinutes(0); + if (step >= timeUnitSize.day) + d.setUTCHours(0); + if (step >= timeUnitSize.day * 4) + d.setUTCDate(1); + if (step >= timeUnitSize.year) + d.setUTCMonth(0); + + + var carry = 0, v = Number.NaN, prev; + do { + prev = v; + v = d.getTime(); + ticks.push(v); + if (unit == "month") { + if (tickSize < 1) { + // a bit complicated - we'll divide the month + // up but we need to take care of fractions + // so we don't end up in the middle of a day + d.setUTCDate(1); + var start = d.getTime(); + d.setUTCMonth(d.getUTCMonth() + 1); + var end = d.getTime(); + d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); + carry = d.getUTCHours(); + d.setUTCHours(0); + } + else + d.setUTCMonth(d.getUTCMonth() + tickSize); + } + else if (unit == "year") { + d.setUTCFullYear(d.getUTCFullYear() + tickSize); + } + else + d.setTime(v + step); + } while (v < axis.max && v != prev); + + return ticks; + }; + + formatter = function (v, axis) { + var d = new Date(v); + + // first check global format + if (opts.timeformat != null) + return $.plot.formatDate(d, opts.timeformat, opts.monthNames); + + var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; + var span = axis.max - axis.min; + var suffix = (opts.twelveHourClock) ? " %p" : ""; + + if (t < timeUnitSize.minute) + fmt = "%h:%M:%S" + suffix; + else if (t < timeUnitSize.day) { + if (span < 2 * timeUnitSize.day) + fmt = "%h:%M" + suffix; + else + fmt = "%b %d %h:%M" + suffix; + } + else if (t < timeUnitSize.month) + fmt = "%b %d"; + else if (t < timeUnitSize.year) { + if (span < timeUnitSize.year) + fmt = "%b"; + else + fmt = "%b %y"; + } + else + fmt = "%y"; + + return $.plot.formatDate(d, fmt, opts.monthNames); + }; + } + else { + // pretty rounding of base-10 numbers + var maxDec = opts.tickDecimals; + var dec = -Math.floor(Math.log(delta) / Math.LN10); + if (maxDec != null && dec > maxDec) + dec = maxDec; + + magn = Math.pow(10, -dec); + norm = delta / magn; // norm is between 1.0 and 10.0 + + 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.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); + axis.tickSize = opts.tickSize || size; + + generator = function (axis) { + var ticks = []; + + // spew out all possible ticks + var 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; + }; + + formatter = function (v, axis) { + return v.toFixed(axis.tickDecimals); + }; + } + + 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 = generator(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]); + } + + generator = 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 != "time" && opts.tickDecimals == null) { + var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1), + ts = generator(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; + } + } + } + + axis.tickGenerator = generator; + if ($.isFunction(opts.tickFormatter)) + axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; + else + axis.tickFormatter = formatter; + } + + 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({ min: axis.min, max: axis.max }); + 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() { + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + + 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(); + } + + function extractRange(ranges, coord) { + var axis, from, to, key, axes = allAxes(); + + for (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; + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // draw markings + var markings = options.grid.markings; + if (markings) { + if ($.isFunction(markings)) { + var 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); + + if (xrange.from == xrange.to && yrange.from == yrange.to) + continue; + + // then draw + xrange.from = xrange.axis.p2c(xrange.from); + xrange.to = xrange.axis.p2c(xrange.to); + yrange.from = yrange.axis.p2c(yrange.from); + yrange.to = yrange.axis.p2c(yrange.to); + + if (xrange.from == xrange.to || yrange.from == yrange.to) { + // draw line + ctx.beginPath(); + ctx.strokeStyle = m.color || options.grid.markingsColor; + ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; + ctx.moveTo(xrange.from, yrange.from); + ctx.lineTo(xrange.to, yrange.to); + ctx.stroke(); + } + else { + // fill area + ctx.fillStyle = m.color || options.grid.markingsColor; + ctx.fillRect(xrange.from, yrange.to, + xrange.to - xrange.from, + yrange.from - yrange.to); + } + } + } + + // draw the ticks + var 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.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString(); + 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.beginPath(); + xoff = yoff = 0; + if (axis.direction == "x") + xoff = plotWidth; + else + yoff = plotHeight; + + if (ctx.lineWidth == 1) { + x = Math.floor(x) + 0.5; + y = Math.floor(y) + 0.5; + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + ctx.stroke(); + } + + // draw ticks + ctx.beginPath(); + for (i = 0; i < axis.ticks.length; ++i) { + var v = axis.ticks[i].v; + + xoff = yoff = 0; + + if (v < axis.min || v > axis.max + // skip those lying on the axes if we got a border + || (t == "full" && 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) { + ctx.lineWidth = bw; + ctx.strokeStyle = options.grid.borderColor; + ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); + } + + ctx.restore(); + } + + function insertAxisLabels() { + placeholder.find(".tickLabels").remove(); + + var html = ['<div class="tickLabels" style="font-size:smaller">']; + + var axes = allAxes(); + for (var j = 0; j < axes.length; ++j) { + var axis = axes[j], box = axis.box; + if (!axis.show) + continue; + //debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px"></div>') + html.push('<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis" style="color:' + axis.options.color + '">'); + for (var i = 0; i < axis.ticks.length; ++i) { + var tick = axis.ticks[i]; + if (!tick.label || tick.v < axis.min || tick.v > axis.max) + continue; + + var pos = {}, align; + + if (axis.direction == "x") { + align = "center"; + pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2); + if (axis.position == "bottom") + pos.top = box.top + box.padding; + else + pos.bottom = canvasHeight - (box.top + box.height - box.padding); + } + else { + pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2); + if (axis.position == "left") { + pos.right = canvasWidth - (box.left + box.width - box.padding) + align = "right"; + } + else { + pos.left = box.left + box.padding; + align = "left"; + } + } + + pos.width = axis.labelWidth; + + var style = ["position:absolute", "text-align:" + align ]; + for (var a in pos) + style.push(a + ":" + pos[a] + "px") + + html.push('<div class="tickLabel" style="' + style.join(';') + '">' + tick.label + '</div>'); + } + html.push('</div>'); + } + + html.push('</div>'); + + placeholder.append(html.join("")); + } + + 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 (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, offset, 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.beginPath(); + c.moveTo(left, bottom); + c.lineTo(left, top); + c.lineTo(right, top); + c.lineTo(right, bottom); + c.fillStyle = fillStyleCallback(bottom, top); + c.fill(); + } + + // draw outline + if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { + c.beginPath(); + + // FIXME: inline moveTo is buggy with excanvas + c.moveTo(left, bottom + offset); + if (drawLeft) + c.lineTo(left, top + offset); + else + c.moveTo(left, top + offset); + if (drawTop) + c.lineTo(right, top + offset); + else + c.moveTo(right, top + offset); + if (drawRight) + c.lineTo(right, bottom + offset); + else + c.moveTo(right, bottom + offset); + if (drawBottom) + c.lineTo(left, bottom + offset); + else + c.moveTo(left, bottom + offset); + c.stroke(); + } + } + + function drawSeriesBars(series) { + function plotBars(datapoints, barLeft, barRight, offset, 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, offset, 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 = series.bars.align == "left" ? 0 : -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, 0, 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() { + placeholder.find(".legend").remove(); + + if (!options.legend.show) + return; + + var fragments = [], rowStarted = false, + lf = options.legend.labelFormatter, s, label; + for (var i = 0; i < series.length; ++i) { + s = series[i]; + label = s.label; + if (!label) + continue; + + if (i % options.legend.noColumns == 0) { + if (rowStarted) + fragments.push('</tr>'); + fragments.push('<tr>'); + rowStarted = true; + } + + if (lf) + label = lf(label, s); + + fragments.push( + '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></div></td>' + + '<td class="legendLabel">' + 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; + + 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, + ps = s.datapoints.pointsize, + mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster + my = axisy.c2p(mouseY), + maxx = maxDistance / axisx.scale, + maxy = maxDistance / axisy.scale; + + // 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 = s.bars.align == "left" ? 0 : -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); + item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); + } + + 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() { + if (!redrawTimeout) + redrawTimeout = setTimeout(drawOverlay, 30); + } + + function drawOverlay() { + redrawTimeout = null; + + // draw highlights + octx.save(); + octx.clearRect(0, 0, canvasWidth, canvasHeight); + 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(); + } + + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") + point = s.data[point]; + + 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; + + 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 = $.color.parse(series.color).scale('a', 0.5).toString(); + 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) { + octx.lineWidth = series.bars.lineWidth; + octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, + 0, 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; + } + } + } + + $.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.7"; + + $.plot.plugins = []; + + // returns a string with the date d formatted according to fmt + $.plot.formatDate = function(d, fmt, monthNames) { + var leftPad = function(n) { + n = "" + n; + return n.length == 1 ? "0" + n : n; + }; + + var r = []; + var escape = false, padNext = false; + var hours = d.getUTCHours(); + var isAM = hours < 12; + if (monthNames == null) + monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + if (fmt.search(/%p|%P/) != -1) { + if (hours > 12) { + hours = hours - 12; + } else if (hours == 0) { + hours = 12; + } + } + for (var i = 0; i < fmt.length; ++i) { + var c = fmt.charAt(i); + + if (escape) { + switch (c) { + case 'h': c = "" + hours; break; + case 'H': c = leftPad(hours); 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; + case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; + case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; + case '0': c = ""; padNext = true; break; + } + if (c && padNext) { + c = leftPad(c); + padNext = false; + } + r.push(c); + if (!padNext) + escape = false; + } + else { + if (c == "%") + escape = true; + else + r.push(c); + } + } + return r.join(""); + }; + + // round to nearby lower multiple of base + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + +})(jQuery); diff --git a/module/web/static/js/libs/jquery.flot.min.js b/module/web/static/js/libs/jquery.flot.min.js deleted file mode 100644 index 4467fc5d8..000000000 --- a/module/web/static/js/libs/jquery.flot.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/* Javascript plotting library for jQuery, v. 0.7. - * - * Released under the MIT license by IOLA, December 2007. - * - */ -(function(b){b.color={};b.color.make=function(d,e,g,f){var c={};c.r=d||0;c.g=e||0;c.b=g||0;c.a=f!=null?f:1;c.add=function(h,j){for(var k=0;k<h.length;++k){c[h.charAt(k)]+=j}return c.normalize()};c.scale=function(h,j){for(var k=0;k<h.length;++k){c[h.charAt(k)]*=j}return c.normalize()};c.toString=function(){if(c.a>=1){return"rgb("+[c.r,c.g,c.b].join(",")+")"}else{return"rgba("+[c.r,c.g,c.b,c.a].join(",")+")"}};c.normalize=function(){function h(k,j,l){return j<k?k:(j>l?l:j)}c.r=h(0,parseInt(c.r),255);c.g=h(0,parseInt(c.g),255);c.b=h(0,parseInt(c.b),255);c.a=h(0,c.a,1);return c};c.clone=function(){return b.color.make(c.r,c.b,c.g,c.a)};return c.normalize()};b.color.extract=function(d,e){var c;do{c=d.css(e).toLowerCase();if(c!=""&&c!="transparent"){break}d=d.parent()}while(!b.nodeName(d.get(0),"body"));if(c=="rgba(0, 0, 0, 0)"){c="transparent"}return b.color.parse(c)};b.color.parse=function(c){var d,f=b.color.make;if(d=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c)){return f(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10))}if(d=/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(c)){return f(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10),parseFloat(d[4]))}if(d=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c)){return f(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55)}if(d=/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(c)){return f(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55,parseFloat(d[4]))}if(d=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c)){return f(parseInt(d[1],16),parseInt(d[2],16),parseInt(d[3],16))}if(d=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c)){return f(parseInt(d[1]+d[1],16),parseInt(d[2]+d[2],16),parseInt(d[3]+d[3],16))}var e=b.trim(c).toLowerCase();if(e=="transparent"){return f(255,255,255,0)}else{d=a[e]||[0,0,0];return f(d[0],d[1],d[2])}};var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function(c){function b(av,ai,J,af){var Q=[],O={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{show:null,position:"bottom",mode:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null,monthNames:null,timeformat:null,twelveHourClock:false},yaxis:{autoscaleMargin:0.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false},shadowSize:3},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},hooks:{}},az=null,ad=null,y=null,H=null,A=null,p=[],aw=[],q={left:0,right:0,top:0,bottom:0},G=0,I=0,h=0,w=0,ak={processOptions:[],processRawData:[],processDatapoints:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},aq=this;aq.setData=aj;aq.setupGrid=t;aq.draw=W;aq.getPlaceholder=function(){return av};aq.getCanvas=function(){return az};aq.getPlotOffset=function(){return q};aq.width=function(){return h};aq.height=function(){return w};aq.offset=function(){var aB=y.offset();aB.left+=q.left;aB.top+=q.top;return aB};aq.getData=function(){return Q};aq.getAxes=function(){var aC={},aB;c.each(p.concat(aw),function(aD,aE){if(aE){aC[aE.direction+(aE.n!=1?aE.n:"")+"axis"]=aE}});return aC};aq.getXAxes=function(){return p};aq.getYAxes=function(){return aw};aq.c2p=C;aq.p2c=ar;aq.getOptions=function(){return O};aq.highlight=x;aq.unhighlight=T;aq.triggerRedrawOverlay=f;aq.pointOffset=function(aB){return{left:parseInt(p[aA(aB,"x")-1].p2c(+aB.x)+q.left),top:parseInt(aw[aA(aB,"y")-1].p2c(+aB.y)+q.top)}};aq.shutdown=ag;aq.resize=function(){B();g(az);g(ad)};aq.hooks=ak;F(aq);Z(J);X();aj(ai);t();W();ah();function an(aD,aB){aB=[aq].concat(aB);for(var aC=0;aC<aD.length;++aC){aD[aC].apply(this,aB)}}function F(){for(var aB=0;aB<af.length;++aB){var aC=af[aB];aC.init(aq);if(aC.options){c.extend(true,O,aC.options)}}}function Z(aC){var aB;c.extend(true,O,aC);if(O.xaxis.color==null){O.xaxis.color=O.grid.color}if(O.yaxis.color==null){O.yaxis.color=O.grid.color}if(O.xaxis.tickColor==null){O.xaxis.tickColor=O.grid.tickColor}if(O.yaxis.tickColor==null){O.yaxis.tickColor=O.grid.tickColor}if(O.grid.borderColor==null){O.grid.borderColor=O.grid.color}if(O.grid.tickColor==null){O.grid.tickColor=c.color.parse(O.grid.color).scale("a",0.22).toString()}for(aB=0;aB<Math.max(1,O.xaxes.length);++aB){O.xaxes[aB]=c.extend(true,{},O.xaxis,O.xaxes[aB])}for(aB=0;aB<Math.max(1,O.yaxes.length);++aB){O.yaxes[aB]=c.extend(true,{},O.yaxis,O.yaxes[aB])}if(O.xaxis.noTicks&&O.xaxis.ticks==null){O.xaxis.ticks=O.xaxis.noTicks}if(O.yaxis.noTicks&&O.yaxis.ticks==null){O.yaxis.ticks=O.yaxis.noTicks}if(O.x2axis){O.xaxes[1]=c.extend(true,{},O.xaxis,O.x2axis);O.xaxes[1].position="top"}if(O.y2axis){O.yaxes[1]=c.extend(true,{},O.yaxis,O.y2axis);O.yaxes[1].position="right"}if(O.grid.coloredAreas){O.grid.markings=O.grid.coloredAreas}if(O.grid.coloredAreasColor){O.grid.markingsColor=O.grid.coloredAreasColor}if(O.lines){c.extend(true,O.series.lines,O.lines)}if(O.points){c.extend(true,O.series.points,O.points)}if(O.bars){c.extend(true,O.series.bars,O.bars)}if(O.shadowSize!=null){O.series.shadowSize=O.shadowSize}for(aB=0;aB<O.xaxes.length;++aB){V(p,aB+1).options=O.xaxes[aB]}for(aB=0;aB<O.yaxes.length;++aB){V(aw,aB+1).options=O.yaxes[aB]}for(var aD in ak){if(O.hooks[aD]&&O.hooks[aD].length){ak[aD]=ak[aD].concat(O.hooks[aD])}}an(ak.processOptions,[O])}function aj(aB){Q=Y(aB);ax();z()}function Y(aE){var aC=[];for(var aB=0;aB<aE.length;++aB){var aD=c.extend(true,{},O.series);if(aE[aB].data!=null){aD.data=aE[aB].data;delete aE[aB].data;c.extend(true,aD,aE[aB]);aE[aB].data=aD.data}else{aD.data=aE[aB]}aC.push(aD)}return aC}function aA(aC,aD){var aB=aC[aD+"axis"];if(typeof aB=="object"){aB=aB.n}if(typeof aB!="number"){aB=1}return aB}function m(){return c.grep(p.concat(aw),function(aB){return aB})}function C(aE){var aC={},aB,aD;for(aB=0;aB<p.length;++aB){aD=p[aB];if(aD&&aD.used){aC["x"+aD.n]=aD.c2p(aE.left)}}for(aB=0;aB<aw.length;++aB){aD=aw[aB];if(aD&&aD.used){aC["y"+aD.n]=aD.c2p(aE.top)}}if(aC.x1!==undefined){aC.x=aC.x1}if(aC.y1!==undefined){aC.y=aC.y1}return aC}function ar(aF){var aD={},aC,aE,aB;for(aC=0;aC<p.length;++aC){aE=p[aC];if(aE&&aE.used){aB="x"+aE.n;if(aF[aB]==null&&aE.n==1){aB="x"}if(aF[aB]!=null){aD.left=aE.p2c(aF[aB]);break}}}for(aC=0;aC<aw.length;++aC){aE=aw[aC];if(aE&&aE.used){aB="y"+aE.n;if(aF[aB]==null&&aE.n==1){aB="y"}if(aF[aB]!=null){aD.top=aE.p2c(aF[aB]);break}}}return aD}function V(aC,aB){if(!aC[aB-1]){aC[aB-1]={n:aB,direction:aC==p?"x":"y",options:c.extend(true,{},aC==p?O.xaxis:O.yaxis)}}return aC[aB-1]}function ax(){var aG;var aM=Q.length,aB=[],aE=[];for(aG=0;aG<Q.length;++aG){var aJ=Q[aG].color;if(aJ!=null){--aM;if(typeof aJ=="number"){aE.push(aJ)}else{aB.push(c.color.parse(Q[aG].color))}}}for(aG=0;aG<aE.length;++aG){aM=Math.max(aM,aE[aG]+1)}var aC=[],aF=0;aG=0;while(aC.length<aM){var aI;if(O.colors.length==aG){aI=c.color.make(100,100,100)}else{aI=c.color.parse(O.colors[aG])}var aD=aF%2==1?-1:1;aI.scale("rgb",1+aD*Math.ceil(aF/2)*0.2);aC.push(aI);++aG;if(aG>=O.colors.length){aG=0;++aF}}var aH=0,aN;for(aG=0;aG<Q.length;++aG){aN=Q[aG];if(aN.color==null){aN.color=aC[aH].toString();++aH}else{if(typeof aN.color=="number"){aN.color=aC[aN.color].toString()}}if(aN.lines.show==null){var aL,aK=true;for(aL in aN){if(aN[aL]&&aN[aL].show){aK=false;break}}if(aK){aN.lines.show=true}}aN.xaxis=V(p,aA(aN,"x"));aN.yaxis=V(aw,aA(aN,"y"))}}function z(){var aO=Number.POSITIVE_INFINITY,aI=Number.NEGATIVE_INFINITY,aB=Number.MAX_VALUE,aU,aS,aR,aN,aD,aJ,aT,aP,aH,aG,aC,a0,aX,aL;function aF(a3,a2,a1){if(a2<a3.datamin&&a2!=-aB){a3.datamin=a2}if(a1>a3.datamax&&a1!=aB){a3.datamax=a1}}c.each(m(),function(a1,a2){a2.datamin=aO;a2.datamax=aI;a2.used=false});for(aU=0;aU<Q.length;++aU){aJ=Q[aU];aJ.datapoints={points:[]};an(ak.processRawData,[aJ,aJ.data,aJ.datapoints])}for(aU=0;aU<Q.length;++aU){aJ=Q[aU];var aZ=aJ.data,aW=aJ.datapoints.format;if(!aW){aW=[];aW.push({x:true,number:true,required:true});aW.push({y:true,number:true,required:true});if(aJ.bars.show||(aJ.lines.show&&aJ.lines.fill)){aW.push({y:true,number:true,required:false,defaultValue:0});if(aJ.bars.horizontal){delete aW[aW.length-1].y;aW[aW.length-1].x=true}}aJ.datapoints.format=aW}if(aJ.datapoints.pointsize!=null){continue}aJ.datapoints.pointsize=aW.length;aP=aJ.datapoints.pointsize;aT=aJ.datapoints.points;insertSteps=aJ.lines.show&&aJ.lines.steps;aJ.xaxis.used=aJ.yaxis.used=true;for(aS=aR=0;aS<aZ.length;++aS,aR+=aP){aL=aZ[aS];var aE=aL==null;if(!aE){for(aN=0;aN<aP;++aN){a0=aL[aN];aX=aW[aN];if(aX){if(aX.number&&a0!=null){a0=+a0;if(isNaN(a0)){a0=null}else{if(a0==Infinity){a0=aB}else{if(a0==-Infinity){a0=-aB}}}}if(a0==null){if(aX.required){aE=true}if(aX.defaultValue!=null){a0=aX.defaultValue}}}aT[aR+aN]=a0}}if(aE){for(aN=0;aN<aP;++aN){a0=aT[aR+aN];if(a0!=null){aX=aW[aN];if(aX.x){aF(aJ.xaxis,a0,a0)}if(aX.y){aF(aJ.yaxis,a0,a0)}}aT[aR+aN]=null}}else{if(insertSteps&&aR>0&&aT[aR-aP]!=null&&aT[aR-aP]!=aT[aR]&&aT[aR-aP+1]!=aT[aR+1]){for(aN=0;aN<aP;++aN){aT[aR+aP+aN]=aT[aR+aN]}aT[aR+1]=aT[aR-aP+1];aR+=aP}}}}for(aU=0;aU<Q.length;++aU){aJ=Q[aU];an(ak.processDatapoints,[aJ,aJ.datapoints])}for(aU=0;aU<Q.length;++aU){aJ=Q[aU];aT=aJ.datapoints.points,aP=aJ.datapoints.pointsize;var aK=aO,aQ=aO,aM=aI,aV=aI;for(aS=0;aS<aT.length;aS+=aP){if(aT[aS]==null){continue}for(aN=0;aN<aP;++aN){a0=aT[aS+aN];aX=aW[aN];if(!aX||a0==aB||a0==-aB){continue}if(aX.x){if(a0<aK){aK=a0}if(a0>aM){aM=a0}}if(aX.y){if(a0<aQ){aQ=a0}if(a0>aV){aV=a0}}}}if(aJ.bars.show){var aY=aJ.bars.align=="left"?0:-aJ.bars.barWidth/2;if(aJ.bars.horizontal){aQ+=aY;aV+=aY+aJ.bars.barWidth}else{aK+=aY;aM+=aY+aJ.bars.barWidth}}aF(aJ.xaxis,aK,aM);aF(aJ.yaxis,aQ,aV)}c.each(m(),function(a1,a2){if(a2.datamin==aO){a2.datamin=null}if(a2.datamax==aI){a2.datamax=null}})}function j(aB,aC){var aD=document.createElement("canvas");aD.className=aC;aD.width=G;aD.height=I;if(!aB){c(aD).css({position:"absolute",left:0,top:0})}c(aD).appendTo(av);if(!aD.getContext){aD=window.G_vmlCanvasManager.initElement(aD)}aD.getContext("2d").save();return aD}function B(){G=av.width();I=av.height();if(G<=0||I<=0){throw"Invalid dimensions for plot, width = "+G+", height = "+I}}function g(aC){if(aC.width!=G){aC.width=G}if(aC.height!=I){aC.height=I}var aB=aC.getContext("2d");aB.restore();aB.save()}function X(){var aC,aB=av.children("canvas.base"),aD=av.children("canvas.overlay");if(aB.length==0||aD==0){av.html("");av.css({padding:0});if(av.css("position")=="static"){av.css("position","relative")}B();az=j(true,"base");ad=j(false,"overlay");aC=false}else{az=aB.get(0);ad=aD.get(0);aC=true}H=az.getContext("2d");A=ad.getContext("2d");y=c([ad,az]);if(aC){av.data("plot").shutdown();aq.resize();A.clearRect(0,0,G,I);y.unbind();av.children().not([az,ad]).remove()}av.data("plot",aq)}function ah(){if(O.grid.hoverable){y.mousemove(aa);y.mouseleave(l)}if(O.grid.clickable){y.click(R)}an(ak.bindEvents,[y])}function ag(){if(M){clearTimeout(M)}y.unbind("mousemove",aa);y.unbind("mouseleave",l);y.unbind("click",R);an(ak.shutdown,[y])}function r(aG){function aC(aH){return aH}var aF,aB,aD=aG.options.transform||aC,aE=aG.options.inverseTransform;if(aG.direction=="x"){aF=aG.scale=h/Math.abs(aD(aG.max)-aD(aG.min));aB=Math.min(aD(aG.max),aD(aG.min))}else{aF=aG.scale=w/Math.abs(aD(aG.max)-aD(aG.min));aF=-aF;aB=Math.max(aD(aG.max),aD(aG.min))}if(aD==aC){aG.p2c=function(aH){return(aH-aB)*aF}}else{aG.p2c=function(aH){return(aD(aH)-aB)*aF}}if(!aE){aG.c2p=function(aH){return aB+aH/aF}}else{aG.c2p=function(aH){return aE(aB+aH/aF)}}}function L(aD){var aB=aD.options,aF,aJ=aD.ticks||[],aI=[],aE,aK=aB.labelWidth,aG=aB.labelHeight,aC;function aH(aM,aL){return c('<div style="position:absolute;top:-10000px;'+aL+'font-size:smaller"><div class="'+aD.direction+"Axis "+aD.direction+aD.n+'Axis">'+aM.join("")+"</div></div>").appendTo(av)}if(aD.direction=="x"){if(aK==null){aK=Math.floor(G/(aJ.length>0?aJ.length:1))}if(aG==null){aI=[];for(aF=0;aF<aJ.length;++aF){aE=aJ[aF].label;if(aE){aI.push('<div class="tickLabel" style="float:left;width:'+aK+'px">'+aE+"</div>")}}if(aI.length>0){aI.push('<div style="clear:left"></div>');aC=aH(aI,"width:10000px;");aG=aC.height();aC.remove()}}}else{if(aK==null||aG==null){for(aF=0;aF<aJ.length;++aF){aE=aJ[aF].label;if(aE){aI.push('<div class="tickLabel">'+aE+"</div>")}}if(aI.length>0){aC=aH(aI,"");if(aK==null){aK=aC.children().width()}if(aG==null){aG=aC.find("div.tickLabel").height()}aC.remove()}}}if(aK==null){aK=0}if(aG==null){aG=0}aD.labelWidth=aK;aD.labelHeight=aG}function au(aD){var aC=aD.labelWidth,aL=aD.labelHeight,aH=aD.options.position,aF=aD.options.tickLength,aG=O.grid.axisMargin,aJ=O.grid.labelMargin,aK=aD.direction=="x"?p:aw,aE;var aB=c.grep(aK,function(aN){return aN&&aN.options.position==aH&&aN.reserveSpace});if(c.inArray(aD,aB)==aB.length-1){aG=0}if(aF==null){aF="full"}var aI=c.grep(aK,function(aN){return aN&&aN.reserveSpace});var aM=c.inArray(aD,aI)==0;if(!aM&&aF=="full"){aF=5}if(!isNaN(+aF)){aJ+=+aF}if(aD.direction=="x"){aL+=aJ;if(aH=="bottom"){q.bottom+=aL+aG;aD.box={top:I-q.bottom,height:aL}}else{aD.box={top:q.top+aG,height:aL};q.top+=aL+aG}}else{aC+=aJ;if(aH=="left"){aD.box={left:q.left+aG,width:aC};q.left+=aC+aG}else{q.right+=aC+aG;aD.box={left:G-q.right,width:aC}}}aD.position=aH;aD.tickLength=aF;aD.box.padding=aJ;aD.innermost=aM}function U(aB){if(aB.direction=="x"){aB.box.left=q.left;aB.box.width=h}else{aB.box.top=q.top;aB.box.height=w}}function t(){var aC,aE=m();c.each(aE,function(aF,aG){aG.show=aG.options.show;if(aG.show==null){aG.show=aG.used}aG.reserveSpace=aG.show||aG.options.reserveSpace;n(aG)});allocatedAxes=c.grep(aE,function(aF){return aF.reserveSpace});q.left=q.right=q.top=q.bottom=0;if(O.grid.show){c.each(allocatedAxes,function(aF,aG){S(aG);P(aG);ap(aG,aG.ticks);L(aG)});for(aC=allocatedAxes.length-1;aC>=0;--aC){au(allocatedAxes[aC])}var aD=O.grid.minBorderMargin;if(aD==null){aD=0;for(aC=0;aC<Q.length;++aC){aD=Math.max(aD,Q[aC].points.radius+Q[aC].points.lineWidth/2)}}for(var aB in q){q[aB]+=O.grid.borderWidth;q[aB]=Math.max(aD,q[aB])}}h=G-q.left-q.right;w=I-q.bottom-q.top;c.each(aE,function(aF,aG){r(aG)});if(O.grid.show){c.each(allocatedAxes,function(aF,aG){U(aG)});k()}o()}function n(aE){var aF=aE.options,aD=+(aF.min!=null?aF.min:aE.datamin),aB=+(aF.max!=null?aF.max:aE.datamax),aH=aB-aD;if(aH==0){var aC=aB==0?1:0.01;if(aF.min==null){aD-=aC}if(aF.max==null||aF.min!=null){aB+=aC}}else{var aG=aF.autoscaleMargin;if(aG!=null){if(aF.min==null){aD-=aH*aG;if(aD<0&&aE.datamin!=null&&aE.datamin>=0){aD=0}}if(aF.max==null){aB+=aH*aG;if(aB>0&&aE.datamax!=null&&aE.datamax<=0){aB=0}}}}aE.min=aD;aE.max=aB}function S(aG){var aM=aG.options;var aH;if(typeof aM.ticks=="number"&&aM.ticks>0){aH=aM.ticks}else{aH=0.3*Math.sqrt(aG.direction=="x"?G:I)}var aT=(aG.max-aG.min)/aH,aO,aB,aN,aR,aS,aQ,aI;if(aM.mode=="time"){var aJ={second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000};var aK=[[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"],[3,"month"],[6,"month"],[1,"year"]];var aC=0;if(aM.minTickSize!=null){if(typeof aM.tickSize=="number"){aC=aM.tickSize}else{aC=aM.minTickSize[0]*aJ[aM.minTickSize[1]]}}for(var aS=0;aS<aK.length-1;++aS){if(aT<(aK[aS][0]*aJ[aK[aS][1]]+aK[aS+1][0]*aJ[aK[aS+1][1]])/2&&aK[aS][0]*aJ[aK[aS][1]]>=aC){break}}aO=aK[aS][0];aN=aK[aS][1];if(aN=="year"){aQ=Math.pow(10,Math.floor(Math.log(aT/aJ.year)/Math.LN10));aI=(aT/aJ.year)/aQ;if(aI<1.5){aO=1}else{if(aI<3){aO=2}else{if(aI<7.5){aO=5}else{aO=10}}}aO*=aQ}aG.tickSize=aM.tickSize||[aO,aN];aB=function(aX){var a2=[],a0=aX.tickSize[0],a3=aX.tickSize[1],a1=new Date(aX.min);var aW=a0*aJ[a3];if(a3=="second"){a1.setUTCSeconds(a(a1.getUTCSeconds(),a0))}if(a3=="minute"){a1.setUTCMinutes(a(a1.getUTCMinutes(),a0))}if(a3=="hour"){a1.setUTCHours(a(a1.getUTCHours(),a0))}if(a3=="month"){a1.setUTCMonth(a(a1.getUTCMonth(),a0))}if(a3=="year"){a1.setUTCFullYear(a(a1.getUTCFullYear(),a0))}a1.setUTCMilliseconds(0);if(aW>=aJ.minute){a1.setUTCSeconds(0)}if(aW>=aJ.hour){a1.setUTCMinutes(0)}if(aW>=aJ.day){a1.setUTCHours(0)}if(aW>=aJ.day*4){a1.setUTCDate(1)}if(aW>=aJ.year){a1.setUTCMonth(0)}var a5=0,a4=Number.NaN,aY;do{aY=a4;a4=a1.getTime();a2.push(a4);if(a3=="month"){if(a0<1){a1.setUTCDate(1);var aV=a1.getTime();a1.setUTCMonth(a1.getUTCMonth()+1);var aZ=a1.getTime();a1.setTime(a4+a5*aJ.hour+(aZ-aV)*a0);a5=a1.getUTCHours();a1.setUTCHours(0)}else{a1.setUTCMonth(a1.getUTCMonth()+a0)}}else{if(a3=="year"){a1.setUTCFullYear(a1.getUTCFullYear()+a0)}else{a1.setTime(a4+aW)}}}while(a4<aX.max&&a4!=aY);return a2};aR=function(aV,aY){var a0=new Date(aV);if(aM.timeformat!=null){return c.plot.formatDate(a0,aM.timeformat,aM.monthNames)}var aW=aY.tickSize[0]*aJ[aY.tickSize[1]];var aX=aY.max-aY.min;var aZ=(aM.twelveHourClock)?" %p":"";if(aW<aJ.minute){fmt="%h:%M:%S"+aZ}else{if(aW<aJ.day){if(aX<2*aJ.day){fmt="%h:%M"+aZ}else{fmt="%b %d %h:%M"+aZ}}else{if(aW<aJ.month){fmt="%b %d"}else{if(aW<aJ.year){if(aX<aJ.year){fmt="%b"}else{fmt="%b %y"}}else{fmt="%y"}}}}return c.plot.formatDate(a0,fmt,aM.monthNames)}}else{var aU=aM.tickDecimals;var aP=-Math.floor(Math.log(aT)/Math.LN10);if(aU!=null&&aP>aU){aP=aU}aQ=Math.pow(10,-aP);aI=aT/aQ;if(aI<1.5){aO=1}else{if(aI<3){aO=2;if(aI>2.25&&(aU==null||aP+1<=aU)){aO=2.5;++aP}}else{if(aI<7.5){aO=5}else{aO=10}}}aO*=aQ;if(aM.minTickSize!=null&&aO<aM.minTickSize){aO=aM.minTickSize}aG.tickDecimals=Math.max(0,aU!=null?aU:aP);aG.tickSize=aM.tickSize||aO;aB=function(aX){var aZ=[];var a0=a(aX.min,aX.tickSize),aW=0,aV=Number.NaN,aY;do{aY=aV;aV=a0+aW*aX.tickSize;aZ.push(aV);++aW}while(aV<aX.max&&aV!=aY);return aZ};aR=function(aV,aW){return aV.toFixed(aW.tickDecimals)}}if(aM.alignTicksWithAxis!=null){var aF=(aG.direction=="x"?p:aw)[aM.alignTicksWithAxis-1];if(aF&&aF.used&&aF!=aG){var aL=aB(aG);if(aL.length>0){if(aM.min==null){aG.min=Math.min(aG.min,aL[0])}if(aM.max==null&&aL.length>1){aG.max=Math.max(aG.max,aL[aL.length-1])}}aB=function(aX){var aY=[],aV,aW;for(aW=0;aW<aF.ticks.length;++aW){aV=(aF.ticks[aW].v-aF.min)/(aF.max-aF.min);aV=aX.min+aV*(aX.max-aX.min);aY.push(aV)}return aY};if(aG.mode!="time"&&aM.tickDecimals==null){var aE=Math.max(0,-Math.floor(Math.log(aT)/Math.LN10)+1),aD=aB(aG);if(!(aD.length>1&&/\..*0$/.test((aD[1]-aD[0]).toFixed(aE)))){aG.tickDecimals=aE}}}}aG.tickGenerator=aB;if(c.isFunction(aM.tickFormatter)){aG.tickFormatter=function(aV,aW){return""+aM.tickFormatter(aV,aW)}}else{aG.tickFormatter=aR}}function P(aF){var aH=aF.options.ticks,aG=[];if(aH==null||(typeof aH=="number"&&aH>0)){aG=aF.tickGenerator(aF)}else{if(aH){if(c.isFunction(aH)){aG=aH({min:aF.min,max:aF.max})}else{aG=aH}}}var aE,aB;aF.ticks=[];for(aE=0;aE<aG.length;++aE){var aC=null;var aD=aG[aE];if(typeof aD=="object"){aB=+aD[0];if(aD.length>1){aC=aD[1]}}else{aB=+aD}if(aC==null){aC=aF.tickFormatter(aB,aF)}if(!isNaN(aB)){aF.ticks.push({v:aB,label:aC})}}}function ap(aB,aC){if(aB.options.autoscaleMargin&&aC.length>0){if(aB.options.min==null){aB.min=Math.min(aB.min,aC[0].v)}if(aB.options.max==null&&aC.length>1){aB.max=Math.max(aB.max,aC[aC.length-1].v)}}}function W(){H.clearRect(0,0,G,I);var aC=O.grid;if(aC.show&&aC.backgroundColor){N()}if(aC.show&&!aC.aboveData){ac()}for(var aB=0;aB<Q.length;++aB){an(ak.drawSeries,[H,Q[aB]]);d(Q[aB])}an(ak.draw,[H]);if(aC.show&&aC.aboveData){ac()}}function D(aB,aI){var aE,aH,aG,aD,aF=m();for(i=0;i<aF.length;++i){aE=aF[i];if(aE.direction==aI){aD=aI+aE.n+"axis";if(!aB[aD]&&aE.n==1){aD=aI+"axis"}if(aB[aD]){aH=aB[aD].from;aG=aB[aD].to;break}}}if(!aB[aD]){aE=aI=="x"?p[0]:aw[0];aH=aB[aI+"1"];aG=aB[aI+"2"]}if(aH!=null&&aG!=null&&aH>aG){var aC=aH;aH=aG;aG=aC}return{from:aH,to:aG,axis:aE}}function N(){H.save();H.translate(q.left,q.top);H.fillStyle=am(O.grid.backgroundColor,w,0,"rgba(255, 255, 255, 0)");H.fillRect(0,0,h,w);H.restore()}function ac(){var aF;H.save();H.translate(q.left,q.top);var aH=O.grid.markings;if(aH){if(c.isFunction(aH)){var aK=aq.getAxes();aK.xmin=aK.xaxis.min;aK.xmax=aK.xaxis.max;aK.ymin=aK.yaxis.min;aK.ymax=aK.yaxis.max;aH=aH(aK)}for(aF=0;aF<aH.length;++aF){var aD=aH[aF],aC=D(aD,"x"),aI=D(aD,"y");if(aC.from==null){aC.from=aC.axis.min}if(aC.to==null){aC.to=aC.axis.max}if(aI.from==null){aI.from=aI.axis.min}if(aI.to==null){aI.to=aI.axis.max}if(aC.to<aC.axis.min||aC.from>aC.axis.max||aI.to<aI.axis.min||aI.from>aI.axis.max){continue}aC.from=Math.max(aC.from,aC.axis.min);aC.to=Math.min(aC.to,aC.axis.max);aI.from=Math.max(aI.from,aI.axis.min);aI.to=Math.min(aI.to,aI.axis.max);if(aC.from==aC.to&&aI.from==aI.to){continue}aC.from=aC.axis.p2c(aC.from);aC.to=aC.axis.p2c(aC.to);aI.from=aI.axis.p2c(aI.from);aI.to=aI.axis.p2c(aI.to);if(aC.from==aC.to||aI.from==aI.to){H.beginPath();H.strokeStyle=aD.color||O.grid.markingsColor;H.lineWidth=aD.lineWidth||O.grid.markingsLineWidth;H.moveTo(aC.from,aI.from);H.lineTo(aC.to,aI.to);H.stroke()}else{H.fillStyle=aD.color||O.grid.markingsColor;H.fillRect(aC.from,aI.to,aC.to-aC.from,aI.from-aI.to)}}}var aK=m(),aM=O.grid.borderWidth;for(var aE=0;aE<aK.length;++aE){var aB=aK[aE],aG=aB.box,aQ=aB.tickLength,aN,aL,aP,aJ;if(!aB.show||aB.ticks.length==0){continue}H.strokeStyle=aB.options.tickColor||c.color.parse(aB.options.color).scale("a",0.22).toString();H.lineWidth=1;if(aB.direction=="x"){aN=0;if(aQ=="full"){aL=(aB.position=="top"?0:w)}else{aL=aG.top-q.top+(aB.position=="top"?aG.height:0)}}else{aL=0;if(aQ=="full"){aN=(aB.position=="left"?0:h)}else{aN=aG.left-q.left+(aB.position=="left"?aG.width:0)}}if(!aB.innermost){H.beginPath();aP=aJ=0;if(aB.direction=="x"){aP=h}else{aJ=w}if(H.lineWidth==1){aN=Math.floor(aN)+0.5;aL=Math.floor(aL)+0.5}H.moveTo(aN,aL);H.lineTo(aN+aP,aL+aJ);H.stroke()}H.beginPath();for(aF=0;aF<aB.ticks.length;++aF){var aO=aB.ticks[aF].v;aP=aJ=0;if(aO<aB.min||aO>aB.max||(aQ=="full"&&aM>0&&(aO==aB.min||aO==aB.max))){continue}if(aB.direction=="x"){aN=aB.p2c(aO);aJ=aQ=="full"?-w:aQ;if(aB.position=="top"){aJ=-aJ}}else{aL=aB.p2c(aO);aP=aQ=="full"?-h:aQ;if(aB.position=="left"){aP=-aP}}if(H.lineWidth==1){if(aB.direction=="x"){aN=Math.floor(aN)+0.5}else{aL=Math.floor(aL)+0.5}}H.moveTo(aN,aL);H.lineTo(aN+aP,aL+aJ)}H.stroke()}if(aM){H.lineWidth=aM;H.strokeStyle=O.grid.borderColor;H.strokeRect(-aM/2,-aM/2,h+aM,w+aM)}H.restore()}function k(){av.find(".tickLabels").remove();var aG=['<div class="tickLabels" style="font-size:smaller">'];var aJ=m();for(var aD=0;aD<aJ.length;++aD){var aC=aJ[aD],aF=aC.box;if(!aC.show){continue}aG.push('<div class="'+aC.direction+"Axis "+aC.direction+aC.n+'Axis" style="color:'+aC.options.color+'">');for(var aE=0;aE<aC.ticks.length;++aE){var aH=aC.ticks[aE];if(!aH.label||aH.v<aC.min||aH.v>aC.max){continue}var aK={},aI;if(aC.direction=="x"){aI="center";aK.left=Math.round(q.left+aC.p2c(aH.v)-aC.labelWidth/2);if(aC.position=="bottom"){aK.top=aF.top+aF.padding}else{aK.bottom=I-(aF.top+aF.height-aF.padding)}}else{aK.top=Math.round(q.top+aC.p2c(aH.v)-aC.labelHeight/2);if(aC.position=="left"){aK.right=G-(aF.left+aF.width-aF.padding);aI="right"}else{aK.left=aF.left+aF.padding;aI="left"}}aK.width=aC.labelWidth;var aB=["position:absolute","text-align:"+aI];for(var aL in aK){aB.push(aL+":"+aK[aL]+"px")}aG.push('<div class="tickLabel" style="'+aB.join(";")+'">'+aH.label+"</div>")}aG.push("</div>")}aG.push("</div>");av.append(aG.join(""))}function d(aB){if(aB.lines.show){at(aB)}if(aB.bars.show){e(aB)}if(aB.points.show){ao(aB)}}function at(aE){function aD(aP,aQ,aI,aU,aT){var aV=aP.points,aJ=aP.pointsize,aN=null,aM=null;H.beginPath();for(var aO=aJ;aO<aV.length;aO+=aJ){var aL=aV[aO-aJ],aS=aV[aO-aJ+1],aK=aV[aO],aR=aV[aO+1];if(aL==null||aK==null){continue}if(aS<=aR&&aS<aT.min){if(aR<aT.min){continue}aL=(aT.min-aS)/(aR-aS)*(aK-aL)+aL;aS=aT.min}else{if(aR<=aS&&aR<aT.min){if(aS<aT.min){continue}aK=(aT.min-aS)/(aR-aS)*(aK-aL)+aL;aR=aT.min}}if(aS>=aR&&aS>aT.max){if(aR>aT.max){continue}aL=(aT.max-aS)/(aR-aS)*(aK-aL)+aL;aS=aT.max}else{if(aR>=aS&&aR>aT.max){if(aS>aT.max){continue}aK=(aT.max-aS)/(aR-aS)*(aK-aL)+aL;aR=aT.max}}if(aL<=aK&&aL<aU.min){if(aK<aU.min){continue}aS=(aU.min-aL)/(aK-aL)*(aR-aS)+aS;aL=aU.min}else{if(aK<=aL&&aK<aU.min){if(aL<aU.min){continue}aR=(aU.min-aL)/(aK-aL)*(aR-aS)+aS;aK=aU.min}}if(aL>=aK&&aL>aU.max){if(aK>aU.max){continue}aS=(aU.max-aL)/(aK-aL)*(aR-aS)+aS;aL=aU.max}else{if(aK>=aL&&aK>aU.max){if(aL>aU.max){continue}aR=(aU.max-aL)/(aK-aL)*(aR-aS)+aS;aK=aU.max}}if(aL!=aN||aS!=aM){H.moveTo(aU.p2c(aL)+aQ,aT.p2c(aS)+aI)}aN=aK;aM=aR;H.lineTo(aU.p2c(aK)+aQ,aT.p2c(aR)+aI)}H.stroke()}function aF(aI,aQ,aP){var aW=aI.points,aV=aI.pointsize,aN=Math.min(Math.max(0,aP.min),aP.max),aX=0,aU,aT=false,aM=1,aL=0,aR=0;while(true){if(aV>0&&aX>aW.length+aV){break}aX+=aV;var aZ=aW[aX-aV],aK=aW[aX-aV+aM],aY=aW[aX],aJ=aW[aX+aM];if(aT){if(aV>0&&aZ!=null&&aY==null){aR=aX;aV=-aV;aM=2;continue}if(aV<0&&aX==aL+aV){H.fill();aT=false;aV=-aV;aM=1;aX=aL=aR+aV;continue}}if(aZ==null||aY==null){continue}if(aZ<=aY&&aZ<aQ.min){if(aY<aQ.min){continue}aK=(aQ.min-aZ)/(aY-aZ)*(aJ-aK)+aK;aZ=aQ.min}else{if(aY<=aZ&&aY<aQ.min){if(aZ<aQ.min){continue}aJ=(aQ.min-aZ)/(aY-aZ)*(aJ-aK)+aK;aY=aQ.min}}if(aZ>=aY&&aZ>aQ.max){if(aY>aQ.max){continue}aK=(aQ.max-aZ)/(aY-aZ)*(aJ-aK)+aK;aZ=aQ.max}else{if(aY>=aZ&&aY>aQ.max){if(aZ>aQ.max){continue}aJ=(aQ.max-aZ)/(aY-aZ)*(aJ-aK)+aK;aY=aQ.max}}if(!aT){H.beginPath();H.moveTo(aQ.p2c(aZ),aP.p2c(aN));aT=true}if(aK>=aP.max&&aJ>=aP.max){H.lineTo(aQ.p2c(aZ),aP.p2c(aP.max));H.lineTo(aQ.p2c(aY),aP.p2c(aP.max));continue}else{if(aK<=aP.min&&aJ<=aP.min){H.lineTo(aQ.p2c(aZ),aP.p2c(aP.min));H.lineTo(aQ.p2c(aY),aP.p2c(aP.min));continue}}var aO=aZ,aS=aY;if(aK<=aJ&&aK<aP.min&&aJ>=aP.min){aZ=(aP.min-aK)/(aJ-aK)*(aY-aZ)+aZ;aK=aP.min}else{if(aJ<=aK&&aJ<aP.min&&aK>=aP.min){aY=(aP.min-aK)/(aJ-aK)*(aY-aZ)+aZ;aJ=aP.min}}if(aK>=aJ&&aK>aP.max&&aJ<=aP.max){aZ=(aP.max-aK)/(aJ-aK)*(aY-aZ)+aZ;aK=aP.max}else{if(aJ>=aK&&aJ>aP.max&&aK<=aP.max){aY=(aP.max-aK)/(aJ-aK)*(aY-aZ)+aZ;aJ=aP.max}}if(aZ!=aO){H.lineTo(aQ.p2c(aO),aP.p2c(aK))}H.lineTo(aQ.p2c(aZ),aP.p2c(aK));H.lineTo(aQ.p2c(aY),aP.p2c(aJ));if(aY!=aS){H.lineTo(aQ.p2c(aY),aP.p2c(aJ));H.lineTo(aQ.p2c(aS),aP.p2c(aJ))}}}H.save();H.translate(q.left,q.top);H.lineJoin="round";var aG=aE.lines.lineWidth,aB=aE.shadowSize;if(aG>0&&aB>0){H.lineWidth=aB;H.strokeStyle="rgba(0,0,0,0.1)";var aH=Math.PI/18;aD(aE.datapoints,Math.sin(aH)*(aG/2+aB/2),Math.cos(aH)*(aG/2+aB/2),aE.xaxis,aE.yaxis);H.lineWidth=aB/2;aD(aE.datapoints,Math.sin(aH)*(aG/2+aB/4),Math.cos(aH)*(aG/2+aB/4),aE.xaxis,aE.yaxis)}H.lineWidth=aG;H.strokeStyle=aE.color;var aC=ae(aE.lines,aE.color,0,w);if(aC){H.fillStyle=aC;aF(aE.datapoints,aE.xaxis,aE.yaxis)}if(aG>0){aD(aE.datapoints,0,0,aE.xaxis,aE.yaxis)}H.restore()}function ao(aE){function aH(aN,aM,aU,aK,aS,aT,aQ,aJ){var aR=aN.points,aI=aN.pointsize;for(var aL=0;aL<aR.length;aL+=aI){var aP=aR[aL],aO=aR[aL+1];if(aP==null||aP<aT.min||aP>aT.max||aO<aQ.min||aO>aQ.max){continue}H.beginPath();aP=aT.p2c(aP);aO=aQ.p2c(aO)+aK;if(aJ=="circle"){H.arc(aP,aO,aM,0,aS?Math.PI:Math.PI*2,false)}else{aJ(H,aP,aO,aM,aS)}H.closePath();if(aU){H.fillStyle=aU;H.fill()}H.stroke()}}H.save();H.translate(q.left,q.top);var aG=aE.points.lineWidth,aC=aE.shadowSize,aB=aE.points.radius,aF=aE.points.symbol;if(aG>0&&aC>0){var aD=aC/2;H.lineWidth=aD;H.strokeStyle="rgba(0,0,0,0.1)";aH(aE.datapoints,aB,null,aD+aD/2,true,aE.xaxis,aE.yaxis,aF);H.strokeStyle="rgba(0,0,0,0.2)";aH(aE.datapoints,aB,null,aD/2,true,aE.xaxis,aE.yaxis,aF)}H.lineWidth=aG;H.strokeStyle=aE.color;aH(aE.datapoints,aB,ae(aE.points,aE.color),0,false,aE.xaxis,aE.yaxis,aF);H.restore()}function E(aN,aM,aV,aI,aQ,aF,aD,aL,aK,aU,aR,aC){var aE,aT,aJ,aP,aG,aB,aO,aH,aS;if(aR){aH=aB=aO=true;aG=false;aE=aV;aT=aN;aP=aM+aI;aJ=aM+aQ;if(aT<aE){aS=aT;aT=aE;aE=aS;aG=true;aB=false}}else{aG=aB=aO=true;aH=false;aE=aN+aI;aT=aN+aQ;aJ=aV;aP=aM;if(aP<aJ){aS=aP;aP=aJ;aJ=aS;aH=true;aO=false}}if(aT<aL.min||aE>aL.max||aP<aK.min||aJ>aK.max){return}if(aE<aL.min){aE=aL.min;aG=false}if(aT>aL.max){aT=aL.max;aB=false}if(aJ<aK.min){aJ=aK.min;aH=false}if(aP>aK.max){aP=aK.max;aO=false}aE=aL.p2c(aE);aJ=aK.p2c(aJ);aT=aL.p2c(aT);aP=aK.p2c(aP);if(aD){aU.beginPath();aU.moveTo(aE,aJ);aU.lineTo(aE,aP);aU.lineTo(aT,aP);aU.lineTo(aT,aJ);aU.fillStyle=aD(aJ,aP);aU.fill()}if(aC>0&&(aG||aB||aO||aH)){aU.beginPath();aU.moveTo(aE,aJ+aF);if(aG){aU.lineTo(aE,aP+aF)}else{aU.moveTo(aE,aP+aF)}if(aO){aU.lineTo(aT,aP+aF)}else{aU.moveTo(aT,aP+aF)}if(aB){aU.lineTo(aT,aJ+aF)}else{aU.moveTo(aT,aJ+aF)}if(aH){aU.lineTo(aE,aJ+aF)}else{aU.moveTo(aE,aJ+aF)}aU.stroke()}}function e(aD){function aC(aJ,aI,aL,aG,aK,aN,aM){var aO=aJ.points,aF=aJ.pointsize;for(var aH=0;aH<aO.length;aH+=aF){if(aO[aH]==null){continue}E(aO[aH],aO[aH+1],aO[aH+2],aI,aL,aG,aK,aN,aM,H,aD.bars.horizontal,aD.bars.lineWidth)}}H.save();H.translate(q.left,q.top);H.lineWidth=aD.bars.lineWidth;H.strokeStyle=aD.color;var aB=aD.bars.align=="left"?0:-aD.bars.barWidth/2;var aE=aD.bars.fill?function(aF,aG){return ae(aD.bars,aD.color,aF,aG)}:null;aC(aD.datapoints,aB,aB+aD.bars.barWidth,0,aE,aD.xaxis,aD.yaxis);H.restore()}function ae(aD,aB,aC,aF){var aE=aD.fill;if(!aE){return null}if(aD.fillColor){return am(aD.fillColor,aC,aF,aB)}var aG=c.color.parse(aB);aG.a=typeof aE=="number"?aE:0.4;aG.normalize();return aG.toString()}function o(){av.find(".legend").remove();if(!O.legend.show){return}var aH=[],aF=false,aN=O.legend.labelFormatter,aM,aJ;for(var aE=0;aE<Q.length;++aE){aM=Q[aE];aJ=aM.label;if(!aJ){continue}if(aE%O.legend.noColumns==0){if(aF){aH.push("</tr>")}aH.push("<tr>");aF=true}if(aN){aJ=aN(aJ,aM)}aH.push('<td class="legendColorBox"><div style="border:1px solid '+O.legend.labelBoxBorderColor+';padding:1px"><div style="width:4px;height:0;border:5px solid '+aM.color+';overflow:hidden"></div></div></td><td class="legendLabel">'+aJ+"</td>")}if(aF){aH.push("</tr>")}if(aH.length==0){return}var aL='<table style="font-size:smaller;color:'+O.grid.color+'">'+aH.join("")+"</table>";if(O.legend.container!=null){c(O.legend.container).html(aL)}else{var aI="",aC=O.legend.position,aD=O.legend.margin;if(aD[0]==null){aD=[aD,aD]}if(aC.charAt(0)=="n"){aI+="top:"+(aD[1]+q.top)+"px;"}else{if(aC.charAt(0)=="s"){aI+="bottom:"+(aD[1]+q.bottom)+"px;"}}if(aC.charAt(1)=="e"){aI+="right:"+(aD[0]+q.right)+"px;"}else{if(aC.charAt(1)=="w"){aI+="left:"+(aD[0]+q.left)+"px;"}}var aK=c('<div class="legend">'+aL.replace('style="','style="position:absolute;'+aI+";")+"</div>").appendTo(av);if(O.legend.backgroundOpacity!=0){var aG=O.legend.backgroundColor;if(aG==null){aG=O.grid.backgroundColor;if(aG&&typeof aG=="string"){aG=c.color.parse(aG)}else{aG=c.color.extract(aK,"background-color")}aG.a=1;aG=aG.toString()}var aB=aK.children();c('<div style="position:absolute;width:'+aB.width()+"px;height:"+aB.height()+"px;"+aI+"background-color:"+aG+';"> </div>').prependTo(aK).css("opacity",O.legend.backgroundOpacity)}}}var ab=[],M=null;function K(aI,aG,aD){var aO=O.grid.mouseActiveRadius,a0=aO*aO+1,aY=null,aR=false,aW,aU;for(aW=Q.length-1;aW>=0;--aW){if(!aD(Q[aW])){continue}var aP=Q[aW],aH=aP.xaxis,aF=aP.yaxis,aV=aP.datapoints.points,aT=aP.datapoints.pointsize,aQ=aH.c2p(aI),aN=aF.c2p(aG),aC=aO/aH.scale,aB=aO/aF.scale;if(aH.options.inverseTransform){aC=Number.MAX_VALUE}if(aF.options.inverseTransform){aB=Number.MAX_VALUE}if(aP.lines.show||aP.points.show){for(aU=0;aU<aV.length;aU+=aT){var aK=aV[aU],aJ=aV[aU+1];if(aK==null){continue}if(aK-aQ>aC||aK-aQ<-aC||aJ-aN>aB||aJ-aN<-aB){continue}var aM=Math.abs(aH.p2c(aK)-aI),aL=Math.abs(aF.p2c(aJ)-aG),aS=aM*aM+aL*aL;if(aS<a0){a0=aS;aY=[aW,aU/aT]}}}if(aP.bars.show&&!aY){var aE=aP.bars.align=="left"?0:-aP.bars.barWidth/2,aX=aE+aP.bars.barWidth;for(aU=0;aU<aV.length;aU+=aT){var aK=aV[aU],aJ=aV[aU+1],aZ=aV[aU+2];if(aK==null){continue}if(Q[aW].bars.horizontal?(aQ<=Math.max(aZ,aK)&&aQ>=Math.min(aZ,aK)&&aN>=aJ+aE&&aN<=aJ+aX):(aQ>=aK+aE&&aQ<=aK+aX&&aN>=Math.min(aZ,aJ)&&aN<=Math.max(aZ,aJ))){aY=[aW,aU/aT]}}}}if(aY){aW=aY[0];aU=aY[1];aT=Q[aW].datapoints.pointsize;return{datapoint:Q[aW].datapoints.points.slice(aU*aT,(aU+1)*aT),dataIndex:aU,series:Q[aW],seriesIndex:aW}}return null}function aa(aB){if(O.grid.hoverable){u("plothover",aB,function(aC){return aC.hoverable!=false})}}function l(aB){if(O.grid.hoverable){u("plothover",aB,function(aC){return false})}}function R(aB){u("plotclick",aB,function(aC){return aC.clickable!=false})}function u(aC,aB,aD){var aE=y.offset(),aH=aB.pageX-aE.left-q.left,aF=aB.pageY-aE.top-q.top,aJ=C({left:aH,top:aF});aJ.pageX=aB.pageX;aJ.pageY=aB.pageY;var aK=K(aH,aF,aD);if(aK){aK.pageX=parseInt(aK.series.xaxis.p2c(aK.datapoint[0])+aE.left+q.left);aK.pageY=parseInt(aK.series.yaxis.p2c(aK.datapoint[1])+aE.top+q.top)}if(O.grid.autoHighlight){for(var aG=0;aG<ab.length;++aG){var aI=ab[aG];if(aI.auto==aC&&!(aK&&aI.series==aK.series&&aI.point[0]==aK.datapoint[0]&&aI.point[1]==aK.datapoint[1])){T(aI.series,aI.point)}}if(aK){x(aK.series,aK.datapoint,aC)}}av.trigger(aC,[aJ,aK])}function f(){if(!M){M=setTimeout(s,30)}}function s(){M=null;A.save();A.clearRect(0,0,G,I);A.translate(q.left,q.top);var aC,aB;for(aC=0;aC<ab.length;++aC){aB=ab[aC];if(aB.series.bars.show){v(aB.series,aB.point)}else{ay(aB.series,aB.point)}}A.restore();an(ak.drawOverlay,[A])}function x(aD,aB,aF){if(typeof aD=="number"){aD=Q[aD]}if(typeof aB=="number"){var aE=aD.datapoints.pointsize;aB=aD.datapoints.points.slice(aE*aB,aE*(aB+1))}var aC=al(aD,aB);if(aC==-1){ab.push({series:aD,point:aB,auto:aF});f()}else{if(!aF){ab[aC].auto=false}}}function T(aD,aB){if(aD==null&&aB==null){ab=[];f()}if(typeof aD=="number"){aD=Q[aD]}if(typeof aB=="number"){aB=aD.data[aB]}var aC=al(aD,aB);if(aC!=-1){ab.splice(aC,1);f()}}function al(aD,aE){for(var aB=0;aB<ab.length;++aB){var aC=ab[aB];if(aC.series==aD&&aC.point[0]==aE[0]&&aC.point[1]==aE[1]){return aB}}return -1}function ay(aE,aD){var aC=aD[0],aI=aD[1],aH=aE.xaxis,aG=aE.yaxis;if(aC<aH.min||aC>aH.max||aI<aG.min||aI>aG.max){return}var aF=aE.points.radius+aE.points.lineWidth/2;A.lineWidth=aF;A.strokeStyle=c.color.parse(aE.color).scale("a",0.5).toString();var aB=1.5*aF,aC=aH.p2c(aC),aI=aG.p2c(aI);A.beginPath();if(aE.points.symbol=="circle"){A.arc(aC,aI,aB,0,2*Math.PI,false)}else{aE.points.symbol(A,aC,aI,aB,false)}A.closePath();A.stroke()}function v(aE,aB){A.lineWidth=aE.bars.lineWidth;A.strokeStyle=c.color.parse(aE.color).scale("a",0.5).toString();var aD=c.color.parse(aE.color).scale("a",0.5).toString();var aC=aE.bars.align=="left"?0:-aE.bars.barWidth/2;E(aB[0],aB[1],aB[2]||0,aC,aC+aE.bars.barWidth,0,function(){return aD},aE.xaxis,aE.yaxis,A,aE.bars.horizontal,aE.bars.lineWidth)}function am(aJ,aB,aH,aC){if(typeof aJ=="string"){return aJ}else{var aI=H.createLinearGradient(0,aH,0,aB);for(var aE=0,aD=aJ.colors.length;aE<aD;++aE){var aF=aJ.colors[aE];if(typeof aF!="string"){var aG=c.color.parse(aC);if(aF.brightness!=null){aG=aG.scale("rgb",aF.brightness)}if(aF.opacity!=null){aG.a*=aF.opacity}aF=aG.toString()}aI.addColorStop(aE/(aD-1),aF)}return aI}}}c.plot=function(g,e,d){var f=new b(c(g),e,d,c.plot.plugins);return f};c.plot.version="0.7";c.plot.plugins=[];c.plot.formatDate=function(l,f,h){var o=function(d){d=""+d;return d.length==1?"0"+d:d};var e=[];var p=false,j=false;var n=l.getUTCHours();var k=n<12;if(h==null){h=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}if(f.search(/%p|%P/)!=-1){if(n>12){n=n-12}else{if(n==0){n=12}}}for(var g=0;g<f.length;++g){var m=f.charAt(g);if(p){switch(m){case"h":m=""+n;break;case"H":m=o(n);break;case"M":m=o(l.getUTCMinutes());break;case"S":m=o(l.getUTCSeconds());break;case"d":m=""+l.getUTCDate();break;case"m":m=""+(l.getUTCMonth()+1);break;case"y":m=""+l.getUTCFullYear();break;case"b":m=""+h[l.getUTCMonth()];break;case"p":m=(k)?("am"):("pm");break;case"P":m=(k)?("AM"):("PM");break;case"0":m="";j=true;break}if(m&&j){m=o(m);j=false}e.push(m);if(!j){p=false}}else{if(m=="%"){p=true}else{e.push(m)}}}return e.join("")};function a(e,d){return d*Math.floor(e/d)}})(jQuery);
\ No newline at end of file diff --git a/module/web/static/js/libs/lodash-0.5.2.js b/module/web/static/js/libs/lodash-0.7.0.js index 3c5448223..7843feb80 100644 --- a/module/web/static/js/libs/lodash-0.5.2.js +++ b/module/web/static/js/libs/lodash-0.7.0.js @@ -1,38 +1,13 @@ /*! - * Lo-Dash v0.5.2 <http://lodash.com> - * Copyright 2012 John-David Dalton <http://allyoucanleet.com/> - * Based on Underscore.js 1.3.3, copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. - * <http://documentcloud.github.com/underscore> + * Lo-Dash v0.7.0 <http://lodash.com> + * (c) 2012 John-David Dalton <http://allyoucanleet.com/> + * Based on Underscore.js 1.4.0 <http://underscorejs.org> + * (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. * Available under MIT license <http://lodash.com/license> */ ;(function(window, undefined) { 'use strict'; - /** - * Used to cache the last `_.templateSettings.evaluate` delimiter to avoid - * unnecessarily assigning `reEvaluateDelimiter` a new generated regexp. - * Assigned in `_.template`. - */ - var lastEvaluateDelimiter; - - /** - * Used to cache the last template `options.variable` to avoid unnecessarily - * assigning `reDoubleVariable` a new generated regexp. Assigned in `_.template`. - */ - var lastVariable; - - /** - * Used to match potentially incorrect data object references, like `obj.obj`, - * in compiled templates. Assigned in `_.template`. - */ - var reDoubleVariable; - - /** - * Used to match "evaluate" delimiters, including internal delimiters, - * in template text. Assigned in `_.template`. - */ - var reEvaluateDelimiter; - /** Detect free variable `exports` */ var freeExports = typeof exports == 'object' && exports && (typeof global == 'object' && global && global == global.global && (window = global), exports); @@ -47,11 +22,17 @@ /** Used to generate unique IDs */ var idCounter = 0; + /** Used by `cachedContains` as the default size when optimizations are enabled for large arrays */ + var largeArraySize = 30; + /** Used to restore the original `_` reference in `noConflict` */ var oldDash = window._; /** Used to detect delimiter values that should be processed by `tokenizeEvaluate` */ - var reComplexDelimiter = /[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; + var reComplexDelimiter = /[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; + + /** Used to match HTML entities */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#x27);/g; /** Used to match empty string literals in compiled template source */ var reEmptyStringLeading = /\b__p \+= '';/g, @@ -71,11 +52,11 @@ .replace(/valueOf|for [^\]]+/g, '.+?') + '$' ); - /** Used to match tokens in template text */ - var reToken = /__token__(\d+)/g; + /** Used to ensure capturing order and avoid matches for undefined delimiters */ + var reNoMatch = /($^)/; - /** Used to match unescaped characters in strings for inclusion in HTML */ - var reUnescapedHtml = /[&<"']/g; + /** Used to match HTML characters */ + var reUnescapedHtml = /[&<>"']/g; /** Used to match unescaped characters in compiled string literals */ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; @@ -89,12 +70,6 @@ /** Used to make template sourceURLs easier to identify */ var templateCounter = 0; - /** Used to replace template delimiters */ - var token = '__token__'; - - /** Used to store tokenized template text snippets */ - var tokenized = []; - /** Native method shortcuts */ var concat = ArrayProto.concat, hasOwnProperty = ObjectProto.hasOwnProperty, @@ -105,9 +80,14 @@ /* Native method shortcuts for methods with the same name as other `lodash` methods */ var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind, + nativeFloor = Math.floor, + nativeGetPrototypeOf = reNative.test(nativeGetPrototypeOf = Object.getPrototypeOf) && nativeGetPrototypeOf, nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, nativeIsFinite = window.isFinite, - nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys; + nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys, + nativeMax = Math.max, + nativeMin = Math.min, + nativeRandom = Math.random; /** `Object#toString` result shortcuts */ var argsClass = '[object Arguments]', @@ -126,11 +106,24 @@ /** * Detect the JScript [[DontEnum]] bug: + * * In IE < 9 an objects own properties, shadowing non-enumerable ones, are * made non-enumerable as well. */ var hasDontEnumBug; + /** + * Detect if `Array#shift` and `Array#splice` augment array-like objects + * incorrectly: + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` + * and `splice()` functions that fail to remove the last element, `value[0]`, + * of array-like objects even though the `length` property is set to `0`. + * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + */ + var hasObjectSpliceBug; + /** Detect if own properties are iterated after inherited properties (IE < 9) */ var iteratesOwnLast; @@ -138,13 +131,17 @@ var noArgsEnum = true; (function() { - var props = []; + var object = { '0': 1, 'length': 1 }, + props = []; + function ctor() { this.x = 1; } ctor.prototype = { 'valueOf': 1, 'y': 1 }; for (var prop in new ctor) { props.push(prop); } for (prop in arguments) { noArgsEnum = !prop; } + hasDontEnumBug = (props + '').length < 4; iteratesOwnLast = props[0] != 'x'; + hasObjectSpliceBug = (props.splice.call(object, 0, 1), object[0]); }(1)); /** Detect if an `arguments` object's [[Class]] is unresolvable (Firefox < 4, IE < 9) */ @@ -155,6 +152,7 @@ /** * Detect lack of support for accessing string characters by index: + * * IE < 8 can't access characters by index and IE 8 can only access * characters by index on string literals. */ @@ -175,18 +173,25 @@ /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */ var isKeysFast = nativeKeys && /^.+$|true/.test(nativeKeys + !!window.attachEvent); - /** Detect if sourceURL syntax is usable without erroring */ + /* Detect if strict mode, "use strict", is inferred to be fast (V8) */ + var isStrictFast = !isBindFast; + + /** + * Detect if sourceURL syntax is usable without erroring: + * + * The JS engine in Adobe products, like InDesign, will throw a syntax error + * when it encounters a single line comment beginning with the `@` symbol. + * + * The JS engine in Narwhal will generate the function `function anonymous(){//}` + * and throw a syntax error. + * + * Avoid comments beginning `@` symbols in IE because they are part of its + * non-standard conditional compilation support. + * http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx + */ try { - // The JS engine in Adobe products, like InDesign, will throw a syntax error - // when it encounters a single line comment beginning with the `@` symbol. - // The JS engine in Narwhal will generate the function `function anonymous(){//}` - // and throw a syntax error. In IE, `@` symbols are part of its non-standard - // conditional compilation support. The `@cc_on` statement activates its support - // while the trailing ` !` induces a syntax error to exlude it. Compatibility - // modes in IE > 8 require a space before the `!` to induce a syntax error. - // See http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx - var useSourceURL = (Function('//@cc_on !')(), true); - } catch(e){ } + var useSourceURL = (Function('//@')(), !window.attachEvent); + } catch(e) { } /** Used to identify object classifications that are array-like */ var arrayLikeClasses = {}; @@ -201,19 +206,6 @@ cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true; - /** - * Used to escape characters for inclusion in HTML. - * The `>` and `/` characters don't require escaping in HTML and have no - * special meaning unless they're part of a tag or an unquoted attribute value - * http://mathiasbynens.be/notes/ambiguous-ampersands (semi-related fun fact) - */ - var htmlEscapes = { - '&': '&', - '<': '<', - '"': '"', - "'": ''' - }; - /** Used to determine if values are of the language type Object */ var objectTypes = { 'boolean': false, @@ -243,27 +235,19 @@ * * @name _ * @constructor - * @param {Mixed} value The value to wrap in a `LoDash` instance. - * @returns {Object} Returns a `LoDash` instance. + * @param {Mixed} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns a `lodash` instance. */ function lodash(value) { - // allow invoking `lodash` without the `new` operator - return new LoDash(value); - } - - /** - * Creates a `LoDash` instance that wraps a value to allow chaining. - * - * @private - * @constructor - * @param {Mixed} value The value to wrap. - */ - function LoDash(value) { // exit early if already wrapped - if (value && value._wrapped) { + if (value && value.__wrapped__) { return value; } - this._wrapped = value; + // allow invoking `lodash` without the `new` operator + if (!(this instanceof lodash)) { + return new lodash(value); + } + this.__wrapped__ = value; } /** @@ -331,15 +315,13 @@ 'var index, value, iteratee = <%= firstArg %>, ' + // assign the `result` variable an initial value 'result<% if (init) { %> = <%= init %><% } %>;\n' + - // add code to exit early or do so if the first argument is falsey - '<%= exit %>;\n' + - // add code after the exit snippet but before the iteration branches + // add code before the iteration branches '<%= top %>;\n' + // the following branch is for iterating arrays and array-like objects '<% if (arrayBranch) { %>' + 'var length = iteratee.length; index = -1;' + - ' <% if (objectBranch) { %>\nif (length > -1 && length === length >>> 0) {<% } %>' + + ' <% if (objectBranch) { %>\nif (length === +length) {<% } %>' + // add support for accessing string characters by index if needed ' <% if (noCharByIndex) { %>\n' + @@ -371,6 +353,12 @@ ' } else {' + ' <% } %>' + + // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 + // (if the prototype or a property on the prototype has been set) + // incorrectly sets a function's `prototype` property [[Enumerable]] + // value to `true`. Because of this Lo-Dash standardizes on skipping + // the the `prototype` property of functions regardless of its + // [[Enumerable]] value. ' <% if (!hasDontEnumBug) { %>\n' + ' var skipProto = typeof iteratee == \'function\' && \n' + ' propertyIsEnumerable.call(iteratee, \'prototype\');\n' + @@ -393,26 +381,16 @@ // else using a for-in loop ' <% } else { %>\n' + ' <%= objectBranch.beforeLoop %>;\n' + - ' for (index in iteratee) {' + - ' <% if (hasDontEnumBug) { %>\n' + - ' <% if (useHas) { %>if (hasOwnProperty.call(iteratee, index)) {\n <% } %>' + - ' value = iteratee[index];\n' + - ' <%= objectBranch.inLoop %>;\n' + - ' <% if (useHas) { %>}<% } %>' + - - // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 - // (if the prototype or a property on the prototype has been set) - // incorrectly sets a function's `prototype` property [[Enumerable]] - // value to `true`. Because of this Lo-Dash standardizes on skipping - // the the `prototype` property of functions regardless of its - // [[Enumerable]] value. - ' <% } else { %>\n' + - ' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> &&\n' + - ' hasOwnProperty.call(iteratee, index)<% } %>) {\n' + - ' value = iteratee[index];\n' + - ' <%= objectBranch.inLoop %>\n' + - ' }' + + ' for (index in iteratee) {<%' + + ' if (!hasDontEnumBug || useHas) { %>\n if (<%' + + ' if (!hasDontEnumBug) { %>!(skipProto && index == \'prototype\')<% }' + + ' if (!hasDontEnumBug && useHas) { %> && <% }' + + ' if (useHas) { %>hasOwnProperty.call(iteratee, index)<% }' + + ' %>) {' + ' <% } %>\n' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>;' + + ' <% if (!hasDontEnumBug || useHas) { %>\n }<% } %>\n' + ' }' + ' <% } %>' + @@ -444,36 +422,22 @@ /** * Reusable iterator options shared by - * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `groupBy`, `map`, - * `reject`, `some`, and `sortBy`. + * `countBy`, `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `groupBy`, + * `map`, `reject`, `some`, and `sortBy`. */ var baseIteratorOptions = { 'args': 'collection, callback, thisArg', 'init': 'collection', - 'top': - 'if (!callback) {\n' + - ' callback = identity\n' + - '}\n' + - 'else if (thisArg) {\n' + - ' callback = iteratorBind(callback, thisArg)\n' + - '}', + 'top': 'callback = createCallback(callback, thisArg)', 'inLoop': 'if (callback(value, index, collection) === false) return result' }; /** Reusable iterator options for `countBy`, `groupBy`, and `sortBy` */ var countByIteratorOptions = { 'init': '{}', - 'top': - 'var prop;\n' + - 'if (typeof callback != \'function\') {\n' + - ' var valueProp = callback;\n' + - ' callback = function(value) { return value[valueProp] }\n' + - '}\n' + - 'else if (thisArg) {\n' + - ' callback = iteratorBind(callback, thisArg)\n' + - '}', + 'top': 'callback = createCallback(callback, thisArg)', 'inLoop': - 'prop = callback(value, index, collection);\n' + + 'var prop = callback(value, index, collection);\n' + '(hasOwnProperty.call(result, prop) ? result[prop]++ : result[prop] = 1)' }; @@ -504,7 +468,7 @@ /** Reusable iterator options for `find`, `forEach`, `forIn`, and `forOwn` */ var forEachIteratorOptions = { - 'top': 'if (thisArg) callback = iteratorBind(callback, thisArg)' + 'top': 'callback = createCallback(callback, thisArg)' }; /** Reusable iterator options for `forIn` and `forOwn` */ @@ -517,7 +481,6 @@ /** Reusable iterator options for `invoke`, `map`, `pluck`, and `sortBy` */ var mapIteratorOptions = { 'init': '', - 'exit': 'if (!collection) return []', 'beforeLoop': { 'array': 'result = Array(length)', 'object': 'result = ' + (isKeysFast ? 'Array(length)' : '[]') @@ -528,10 +491,26 @@ } }; + /** Reusable iterator options for `omit` and `pick` */ + var omitIteratorOptions = { + 'useHas': false, + 'args': 'object, callback, thisArg', + 'init': '{}', + 'top': + 'var isFunc = typeof callback == \'function\';\n' + + 'if (isFunc) callback = createCallback(callback, thisArg);\n' + + 'else var props = concat.apply(ArrayProto, arguments)', + 'inLoop': + 'if (isFunc\n' + + ' ? !callback(value, index, object)\n' + + ' : indexOf(props, index) < 0\n' + + ') result[index] = value' + }; + /*--------------------------------------------------------------------------*/ /** - * Creates a new function optimized for searching large arrays for a given `value`, + * Creates a function optimized for searching large arrays for a given `value`, * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`. * * @private @@ -545,7 +524,7 @@ fromIndex || (fromIndex = 0); var length = array.length, - isLarge = (length - fromIndex) >= (largeSize || 30), + isLarge = (length - fromIndex) >= (largeSize || largeArraySize), cache = isLarge ? {} : array; if (isLarge) { @@ -570,6 +549,114 @@ } /** + * Used by `sortBy` to compare transformed `collection` values, stable sorting + * them in ascending order. + * + * @private + * @param {Object} a The object to compare to `b`. + * @param {Object} b The object to compare to `a`. + * @returns {Number} Returns the sort order indicator of `1` or `-1`. + */ + function compareAscending(a, b) { + var ai = a.index, + bi = b.index; + + a = a.criteria; + b = b.criteria; + + // ensure a stable sort in V8 and other engines + // http://code.google.com/p/v8/issues/detail?id=90 + if (a !== b) { + if (a > b || a === undefined) { + return 1; + } + if (a < b || b === undefined) { + return -1; + } + } + return ai < bi ? -1 : 1; + } + + /** + * Creates a function that, when called, invokes `func` with the `this` + * binding of `thisArg` and prepends any `partailArgs` to the arguments passed + * to the bound function. + * + * @private + * @param {Function|String} func The function to bind or the method name. + * @param {Mixed} [thisArg] The `this` binding of `func`. + * @param {Array} partialArgs An array of arguments to be partially applied. + * @returns {Function} Returns the new bound function. + */ + function createBound(func, thisArg, partialArgs) { + var isFunc = isFunction(func), + isPartial = !partialArgs, + methodName = func; + + // juggle arguments + if (isPartial) { + partialArgs = thisArg; + } + + function bound() { + // `Function#bind` spec + // http://es5.github.com/#x15.3.4.5 + var args = arguments, + thisBinding = isPartial ? this : thisArg; + + if (!isFunc) { + func = thisArg[methodName]; + } + if (partialArgs.length) { + args = args.length + ? partialArgs.concat(slice.call(args)) + : partialArgs; + } + if (this instanceof bound) { + // get `func` instance if `bound` is invoked in a `new` expression + noop.prototype = func.prototype; + thisBinding = new noop; + + // mimic the constructor's `return` behavior + // http://es5.github.com/#x13.2.2 + var result = func.apply(thisBinding, args); + return result && objectTypes[typeof result] + ? result + : thisBinding + } + return func.apply(thisBinding, args); + } + return bound; + } + + /** + * Produces an iteration callback bound to an optional `thisArg`. If `func` is + * a property name, the callback will return the property value for a given element. + * + * @private + * @param {Function|String} [func=identity|property] The function called per + * iteration or property name to query. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Function} Returns a callback function. + */ + function createCallback(func, thisArg) { + if (!func) { + return identity; + } + if (typeof func != 'function') { + return function(object) { + return object[func]; + }; + } + if (thisArg !== undefined) { + return function(value, index, object) { + return func.call(thisArg, value, index, object); + }; + } + return func; + } + + /** * Creates compiled iteration functions. The iteration function will be created * to iterate over only objects if the first argument of `options.args` is * "object" or `options.inLoop.array` is falsey. @@ -588,11 +675,7 @@ * * init - A string to specify the initial value of the `result` variable. * - * exit - A string of code to use in place of the default exit-early check - * of `if (!arguments[0]) return result`. - * - * top - A string of code to execute after the exit-early check but before - * the iteration branches. + * top - A string of code to execute before the iteration branches. * * beforeLoop - A string or object containing an "array" or "object" property * of code to execute before the array or object loops. @@ -615,8 +698,6 @@ // merge options into a template data object var data = { 'bottom': '', - 'exit': '', - 'init': '', 'top': '', 'arrayBranch': { 'beforeLoop': '' }, 'objectBranch': { 'beforeLoop': '' } @@ -631,8 +712,8 @@ if (typeof value == 'string') { value = { 'array': value, 'object': value }; } - data.arrayBranch[prop] = value.array; - data.objectBranch[prop] = value.object; + data.arrayBranch[prop] = value.array || ''; + data.objectBranch[prop] = value.object || ''; } else { data[prop] = value; } @@ -640,83 +721,44 @@ } // set additional template `data` values var args = data.args, - firstArg = /^[^,]+/.exec(args)[0]; + firstArg = /^[^,]+/.exec(args)[0], + init = data.init, + useStrict = data.useStrict; data.firstArg = firstArg; data.hasDontEnumBug = hasDontEnumBug; + data.init = init == null ? firstArg : init; data.isKeysFast = isKeysFast; data.noArgsEnum = noArgsEnum; data.shadowed = shadowed; data.useHas = data.useHas !== false; - data.useStrict = data.useStrict !== false; + data.useStrict = useStrict == null ? isStrictFast : useStrict; - if (!('noCharByIndex' in data)) { + if (data.noCharByIndex == null) { data.noCharByIndex = noCharByIndex; } - if (!data.exit) { - data.exit = 'if (!' + firstArg + ') return result'; - } if (firstArg != 'collection' || !data.arrayBranch.inLoop) { data.arrayBranch = null; } // create the function factory var factory = Function( - 'arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, ' + - 'hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, ' + - 'isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys, ' + - 'propertyIsEnumerable, slice, stringClass, toString', + 'arrayLikeClasses, ArrayProto, bind, compareAscending, concat, createCallback, ' + + 'forIn, hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, ' + + 'isPlainObject, objectClass, objectTypes, nativeKeys, propertyIsEnumerable, ' + + 'slice, stringClass, toString, undefined', 'var callee = function(' + args + ') {\n' + iteratorTemplate(data) + '\n};\n' + 'return callee' ); // return the compiled function return factory( - arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, - hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, - isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys, - propertyIsEnumerable, slice, stringClass, toString + arrayLikeClasses, ArrayProto, bind, compareAscending, concat, createCallback, + forIn, hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, + isPlainObject, objectClass, objectTypes, nativeKeys, propertyIsEnumerable, + slice, stringClass, toString ); } /** - * Used by `sortBy` to compare transformed `collection` values, stable sorting - * them in ascending order. - * - * @private - * @param {Object} a The object to compare to `b`. - * @param {Object} b The object to compare to `a`. - * @returns {Number} Returns the sort order indicator of `1` or `-1`. - */ - function compareAscending(a, b) { - var ai = a.index, - bi = b.index; - - a = a.criteria; - b = b.criteria; - - if (a === undefined) { - return 1; - } - if (b === undefined) { - return -1; - } - // ensure a stable sort in V8 and other engines - // http://code.google.com/p/v8/issues/detail?id=90 - return a < b ? -1 : a > b ? 1 : ai < bi ? -1 : 1; - } - - /** - * Used by `template` to replace tokens with their corresponding code snippets. - * - * @private - * @param {String} match The matched token. - * @param {String} index The `tokenized` index of the code snippet. - * @returns {String} Returns the code snippet. - */ - function detokenize(match, index) { - return tokenized[index]; - } - - /** * Used by `template` to escape characters for inclusion in compiled * string literals. * @@ -729,7 +771,7 @@ } /** - * Used by `escape` to escape characters for inclusion in HTML. + * Used by `escape` to convert characters to HTML entities. * * @private * @param {String} match The matched character to escape. @@ -740,66 +782,6 @@ } /** - * Checks if a given `value` is an object created by the `Object` constructor - * assuming objects created by the `Object` constructor have no inherited - * enumerable properties and that there are no `Object.prototype` extensions. - * - * @private - * @param {Mixed} value The value to check. - * @param {Boolean} [skipArgsCheck=false] Internally used to skip checks for - * `arguments` objects. - * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, - * else `false`. - */ - function isPlainObject(value, skipArgsCheck) { - // avoid non-objects and false positives for `arguments` objects - var result = false; - if (!(value && typeof value == 'object') || (!skipArgsCheck && isArguments(value))) { - return result; - } - // IE < 9 presents DOM nodes as `Object` objects except they have `toString` - // methods that are `typeof` "string" and still can coerce nodes to strings. - // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) - var ctor = value.constructor; - if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && - (!isFunction(ctor) || ctor instanceof ctor)) { - // IE < 9 iterates inherited properties before own properties. If the first - // iterated property is an object's own property then there are no inherited - // enumerable properties. - if (iteratesOwnLast) { - forIn(value, function(objValue, objKey) { - result = !hasOwnProperty.call(value, objKey); - return false; - }); - return result === false; - } - // In most environments an object's own properties are iterated before - // its inherited properties. If the last iterated property is an object's - // own property then there are no inherited enumerable properties. - forIn(value, function(objValue, objKey) { - result = objKey; - }); - return result === false || hasOwnProperty.call(value, result); - } - return result; - } - - /** - * Creates a new function that, when called, invokes `func` with the `this` - * binding of `thisArg` and the arguments (value, index, object). - * - * @private - * @param {Function} func The function to bind. - * @param {Mixed} [thisArg] The `this` binding of `func`. - * @returns {Function} Returns the new bound function. - */ - function iteratorBind(func, thisArg) { - return function(value, index, object) { - return func.call(thisArg, value, index, object); - }; - } - - /** * A no-operation function. * * @private @@ -809,62 +791,36 @@ } /** - * Used by `template` to replace "escape" template delimiters with tokens. + * Used by `unescape` to convert HTML entities to characters. * * @private - * @param {String} match The matched template delimiter. - * @param {String} value The delimiter value. - * @returns {String} Returns a token. + * @param {String} match The matched character to unescape. + * @returns {String} Returns the unescaped character. */ - function tokenizeEscape(match, value) { - if (match && reComplexDelimiter.test(value)) { - return '<e%-' + value + '%>'; - } - var index = tokenized.length; - tokenized[index] = "' +\n__e(" + value + ") +\n'"; - return token + index; + function unescapeHtmlChar(match) { + return htmlUnescapes[match]; } - /** - * Used by `template` to replace "evaluate" template delimiters, or complex - * "escape" and "interpolate" delimiters, with tokens. - * - * @private - * @param {String} match The matched template delimiter. - * @param {String} escapeValue The complex "escape" delimiter value. - * @param {String} interpolateValue The complex "interpolate" delimiter value. - * @param {String} [evaluateValue] The "evaluate" delimiter value. - * @returns {String} Returns a token. - */ - function tokenizeEvaluate(match, escapeValue, interpolateValue, evaluateValue) { - if (evaluateValue) { - var index = tokenized.length; - tokenized[index] = "';\n" + evaluateValue + ";\n__p += '"; - return token + index; - } - return escapeValue - ? tokenizeEscape(null, escapeValue) - : tokenizeInterpolate(null, interpolateValue); - } + /*--------------------------------------------------------------------------*/ /** - * Used by `template` to replace "interpolate" template delimiters with tokens. + * Creates an object composed of the inverted keys and values of the given `object`. * - * @private - * @param {String} match The matched template delimiter. - * @param {String} value The delimiter value. - * @returns {String} Returns a token. + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to invert. + * @returns {Object} Returns the created inverted object. + * @example + * + * _.invert({ 'first': 'Moe', 'second': 'Larry', 'third': 'Curly' }); + * // => { 'Moe': 'first', 'Larry': 'second', 'Curly': 'third' } (order is not guaranteed) */ - function tokenizeInterpolate(match, value) { - if (match && reComplexDelimiter.test(value)) { - return '<e%=' + value + '%>'; - } - var index = tokenized.length; - tokenized[index] = "' +\n((__t = (" + value + ")) == null ? '' : __t) +\n'"; - return token + index; - } - - /*--------------------------------------------------------------------------*/ + var invert = createIterator({ + 'args': 'object', + 'init': '{}', + 'inLoop': 'result[value] = index' + }); /** * Checks if `value` is an `arguments` object. @@ -922,7 +878,7 @@ * @returns {Boolean} Returns `true` if the `value` is a function, else `false`. * @example * - * _.isFunction(''.concat); + * _.isFunction(_); * // => true */ function isFunction(value) { @@ -936,6 +892,85 @@ } /** + * Checks if a given `value` is an object created by the `Object` constructor. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Stooge(name, age) { + * this.name = name; + * this.age = age; + * } + * + * _.isPlainObject(new Stooge('moe', 40)); + * // false + * + * _.isPlainObject([1, 2, 3]); + * // false + * + * _.isPlainObject({ 'name': 'moe', 'age': 40 }); + * // => true + */ + var isPlainObject = !nativeGetPrototypeOf ? isPlainFallback : function(value) { + if (!(value && typeof value == 'object')) { + return false; + } + var valueOf = value.valueOf, + objProto = typeof valueOf == 'function' && (objProto = nativeGetPrototypeOf(valueOf)) && nativeGetPrototypeOf(objProto); + + return objProto + ? value == objProto || (nativeGetPrototypeOf(value) == objProto && !isArguments(value)) + : isPlainFallback(value); + }; + + /** + * A fallback implementation of `isPlainObject` that checks if a given `value` + * is an object created by the `Object` constructor, assuming objects created + * by the `Object` constructor have no inherited enumerable properties and that + * there are no `Object.prototype` extensions. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if `value` is a plain object, else `false`. + */ + function isPlainFallback(value) { + // avoid non-objects and false positives for `arguments` objects + var result = false; + if (!(value && typeof value == 'object') || isArguments(value)) { + return result; + } + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings. + // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) + var ctor = value.constructor; + if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && + (!isFunction(ctor) || ctor instanceof ctor)) { + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + if (iteratesOwnLast) { + forIn(value, function(value, key, object) { + result = !hasOwnProperty.call(object, key); + return false; + }); + return result === false; + } + // In most environments an object's own properties are iterated before + // its inherited properties. If the last iterated property is an object's + // own property then there are no inherited enumerable properties. + forIn(value, function(value, key) { + result = key; + }); + return result === false || hasOwnProperty.call(value, result); + } + return result; + } + + /** * A shim implementation of `Object.keys` that produces an array of the given * object's own enumerable property names. * @@ -949,26 +984,43 @@ 'inLoop': 'result.push(index)' }); + /** + * Used to convert characters to HTML entities: + * + * Though the `>` character is escaped for symmetry, characters like `>` and `/` + * don't require escaping in HTML and have no special meaning unless they're part + * of a tag or an unquoted attribute value. + * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact") + */ + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + /** Used to convert HTML entities to characters */ + var htmlUnescapes = invert(htmlEscapes); + /*--------------------------------------------------------------------------*/ /** * Creates a clone of `value`. If `deep` is `true`, all nested objects will - * also be cloned otherwise they will be assigned by reference. If a value has - * a `clone` method it will be used to perform the clone. Functions, DOM nodes, - * `arguments` objects, and objects created by constructors other than `Object` - * are **not** cloned unless they have a custom `clone` method. + * also be cloned otherwise they will be assigned by reference. Functions, DOM + * nodes, `arguments` objects, and objects created by constructors other than + * `Object` are **not** cloned. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to clone. * @param {Boolean} deep A flag to indicate a deep clone. - * @param {Object} [guard] Internally used to allow this method to work with + * @param- {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `deep`. - * @param {Array} [stack=[]] Internally used to keep track of traversed objects - * to avoid circular references. - * @param {Object} thorough Internally used to indicate whether or not to perform - * a more thorough clone of non-object values. + * @param- {Array} [stackA=[]] Internally used to track traversed source objects. + * @param- {Array} [stackB=[]] Internally used to associate clones with their + * source counterparts. * @returns {Mixed} Returns the cloned `value`. * @example * @@ -989,26 +1041,15 @@ * shallow[0] === stooges[0]; * // => false */ - function clone(value, deep, guard, stack, thorough) { + function clone(value, deep, guard, stackA, stackB) { if (value == null) { return value; } if (guard) { deep = false; } - // avoid slower checks on primitives - thorough || (thorough = { 'value': null }); - if (thorough.value == null) { - // primitives passed from iframes use the primary document's native prototypes - thorough.value = !!(BoolProto.clone || NumberProto.clone || StringProto.clone); - } - // use custom `clone` method if available - var isObj = objectTypes[typeof value]; - if ((isObj || thorough.value) && value.clone && isFunction(value.clone)) { - thorough.value = null; - return value.clone(deep); - } // inspect [[Class]] + var isObj = objectTypes[typeof value]; if (isObj) { // don't clone `arguments` objects, functions, or non-object Objects var className = toString.call(value); @@ -1016,7 +1057,7 @@ return value; } var isArr = className == arrayClass; - isObj = isArr || (className == objectClass ? isPlainObject(value, true) : isObj); + isObj = isArr || (className == objectClass ? isPlainObject(value) : isObj); } // shallow clone if (!isObj || !deep) { @@ -1043,30 +1084,33 @@ } // check for circular references and return corresponding clone - stack || (stack = []); - var length = stack.length; + stackA || (stackA = []); + stackB || (stackB = []); + + var length = stackA.length; while (length--) { - if (stack[length].source == value) { - return stack[length].value; + if (stackA[length] == value) { + return stackB[length]; } } // init cloned object - length = value.length; - var result = isArr ? ctor(length) : {}; + var result = isArr ? ctor(length = value.length) : {}; - // add current clone and original source value to the stack of traversed objects - stack.push({ 'value': result, 'source': value }); + // add the source value to the stack of traversed objects + // and associate it with its clone + stackA.push(value); + stackB.push(result); // recursively populate clone (susceptible to call stack limits) if (isArr) { var index = -1; while (++index < length) { - result[index] = clone(value[index], deep, null, stack, thorough); + result[index] = clone(value[index], deep, null, stackA, stackB); } } else { forOwn(value, function(objValue, key) { - result[key] = clone(objValue, deep, null, stack, thorough); + result[key] = clone(objValue, deep, null, stackA, stackB); }); } return result; @@ -1095,30 +1139,6 @@ }); /** - * Creates a shallow clone of `object` excluding the specified properties. - * Property names may be specified as individual arguments or as arrays of - * property names. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The source object. - * @param {Object} [prop1, prop2, ...] The properties to drop. - * @returns {Object} Returns an object without the dropped properties. - * @example - * - * _.drop({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid'); - * // => { 'name': 'moe', 'age': 40 } - */ - var drop = createIterator({ - 'useHas': false, - 'args': 'object', - 'init': '{}', - 'top': 'var props = concat.apply(ArrayProto, arguments)', - 'inLoop': 'if (indexOf(props, index) < 0) result[index] = value' - }); - - /** * Assigns enumerable properties of the source object(s) to the `destination` * object. Subsequent sources will overwrite propery assignments of previous * sources. @@ -1139,7 +1159,7 @@ /** * Iterates over `object`'s own and inherited enumerable properties, executing * the `callback` for each property. The `callback` is bound to `thisArg` and - * invoked with 3 arguments; (value, key, object). Callbacks may exit iteration + * invoked with three arguments; (value, key, object). Callbacks may exit iteration * early by explicitly returning `false`. * * @static @@ -1147,7 +1167,7 @@ * @category Objects * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns `object`. * @example * @@ -1170,16 +1190,16 @@ /** * Iterates over `object`'s own enumerable properties, executing the `callback` - * for each property. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; (value, key, object). Callbacks may exit iteration early by - * explicitly returning `false`. + * for each property. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, key, object). Callbacks may exit iteration early by explicitly + * returning `false`. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns `object`. * @example * @@ -1229,7 +1249,7 @@ * // => true */ function has(object, property) { - return object ? hasOwnProperty.call(object, property) : false; + return hasOwnProperty.call(object, property); } /** @@ -1308,11 +1328,12 @@ 'args': 'value', 'init': 'true', 'top': + 'if (!value) return result;\n' + 'var className = toString.call(value),\n' + ' length = value.length;\n' + 'if (arrayLikeClasses[className]' + (noArgsClass ? ' || isArguments(value)' : '') + ' ||\n' + - ' (className == objectClass && length > -1 && length === length >>> 0 &&\n' + + ' (className == objectClass && length === +length &&\n' + ' isFunction(value.splice))' + ') return !length', 'inLoop': { @@ -1322,18 +1343,15 @@ /** * Performs a deep comparison between two values to determine if they are - * equivalent to each other. If a value has an `isEqual` method it will be - * used to perform the comparison. + * equivalent to each other. * * @static * @memberOf _ * @category Objects * @param {Mixed} a The value to compare. * @param {Mixed} b The other value to compare. - * @param {Array} [stack=[]] Internally used to keep track of traversed objects - * to avoid circular references. - * @param {Object} thorough Internally used to indicate whether or not to perform - * a more thorough comparison of non-object values. + * @param- {Object} [stackA=[]] Internally used track traversed `a` objects. + * @param- {Object} [stackB=[]] Internally used track traversed `b` objects. * @returns {Boolean} Returns `true` if the values are equvalent, else `false`. * @example * @@ -1346,40 +1364,21 @@ * _.isEqual(moe, clone); * // => true */ - function isEqual(a, b, stack, thorough) { + function isEqual(a, b, stackA, stackB) { // a strict comparison is necessary because `null == undefined` if (a == null || b == null) { return a === b; } - // avoid slower checks on non-objects - thorough || (thorough = { 'value': null }); - if (thorough.value == null) { - // primitives passed from iframes use the primary document's native prototypes - thorough.value = !!(BoolProto.isEqual || NumberProto.isEqual || StringProto.isEqual); - } - if (objectTypes[typeof a] || objectTypes[typeof b] || thorough.value) { - // unwrap any LoDash wrapped values - if (a._chain) { - a = a._wrapped; - } - if (b._chain) { - b = b._wrapped; - } - // use custom `isEqual` method if available - if (a.isEqual && isFunction(a.isEqual)) { - thorough.value = null; - return a.isEqual(b); - } - if (b.isEqual && isFunction(b.isEqual)) { - thorough.value = null; - return b.isEqual(a); - } - } // exit early for identical values if (a === b) { // treat `+0` vs. `-0` as not equal return a !== 0 || (1 / a == 1 / b); } + // unwrap any `lodash` wrapped values + if (objectTypes[typeof a] || objectTypes[typeof b]) { + a = a.__wrapped__ || a; + b = b.__wrapped__ || b; + } // compare [[Class]] names var className = toString.call(a); if (className != toString.call(b)) { @@ -1420,11 +1419,13 @@ // assume cyclic structures are equal // the algorithm for detecting cyclic structures is adapted from ES 5.1 // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3) - stack || (stack = []); - var length = stack.length; + stackA || (stackA = []); + stackB || (stackB = []); + + var length = stackA.length; while (length--) { - if (stack[length] == a) { - return true; + if (stackA[length] == a) { + return stackB[length] == b; } } @@ -1432,8 +1433,9 @@ result = true, size = 0; - // add `a` to the stack of traversed objects - stack.push(a); + // add `a` and `b` to the stack of traversed objects + stackA.push(a); + stackB.push(b); // recursively compare objects and arrays (susceptible to call stack limits) if (isArr) { @@ -1444,7 +1446,7 @@ if (result) { // deep compare the contents, ignoring non-numeric properties while (size--) { - if (!(result = isEqual(a[size], b[size], stack, thorough))) { + if (!(result = isEqual(a[size], b[size], stackA, stackB))) { break; } } @@ -1468,7 +1470,7 @@ // count the number of properties. size++; // deep compare each property value. - if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stackA, stackB))) { return false; } } @@ -1488,7 +1490,7 @@ while (++index < 7) { prop = shadowed[index]; if (hasOwnProperty.call(a, prop) && - !(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + !(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stackA, stackB))) { return false; } } @@ -1537,6 +1539,9 @@ * _.isObject({}); * // => true * + * _.isObject([1, 2, 3]); + * // => true + * * _.isObject(1); * // => false */ @@ -1611,7 +1616,7 @@ * @returns {Boolean} Returns `true` if the `value` is a number, else `false`. * @example * - * _.isNumber(8.4 * 5; + * _.isNumber(8.4 * 5); * // => true */ function isNumber(value) { @@ -1684,15 +1689,10 @@ * // => ['one', 'two', 'three'] (order is not guaranteed) */ var keys = !nativeKeys ? shimKeys : function(object) { - var type = typeof object; - // avoid iterating over the `prototype` property - if (type == 'function' && propertyIsEnumerable.call(object, 'prototype')) { - return shimKeys(object); - } - return object && objectTypes[type] - ? nativeKeys(object) - : []; + return typeof object == 'function' && propertyIsEnumerable.call(object, 'prototype') + ? shimKeys(object) + : nativeKeys(object); }; /** @@ -1705,10 +1705,11 @@ * @category Objects * @param {Object} object The destination object. * @param {Object} [source1, source2, ...] The source objects. - * @param {Object} [indicator] Internally used to indicate that the `stack` + * @param- {Object} [indicator] Internally used to indicate that the `stack` * argument is an array of traversed objects instead of another source object. - * @param {Array} [stack=[]] Internally used to keep track of traversed objects - * to avoid circular references. + * @param- {Array} [stackA=[]] Internally used to track traversed source objects. + * @param- {Array} [stackB=[]] Internally used to associate clones with their + * source counterparts. * @returns {Object} Returns the destination object. * @example * @@ -1726,107 +1727,125 @@ * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] */ var merge = createIterator(extendIteratorOptions, { - 'args': 'object, source, indicator, stack', + 'args': 'object, source, indicator', 'top': - 'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' + - 'if (!recursive) stack = [];\n' + - 'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' + - ' if (iteratee = arguments[argsIndex]) {', + 'var isArr, args = arguments, argsIndex = 0;\n' + + 'if (indicator == compareAscending) {\n' + + ' var argsLength = 2, stackA = args[3], stackB = args[4]\n' + + '} else {\n' + + ' var argsLength = args.length, stackA = [], stackB = []\n' + + '}\n' + + 'while (++argsIndex < argsLength) {\n' + + ' if (iteratee = args[argsIndex]) {', 'inLoop': - 'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' + - ' found = false; stackLength = stack.length;\n' + + 'if ((source = value) && ((isArr = isArray(source)) || isPlainObject(source))) {\n' + + ' var found = false, stackLength = stackA.length;\n' + ' while (stackLength--) {\n' + - ' if (found = stack[stackLength].source == value) break\n' + + ' if (found = stackA[stackLength] == source) break\n' + ' }\n' + ' if (found) {\n' + - ' result[index] = stack[stackLength].value\n' + + ' result[index] = stackB[stackLength]\n' + ' } else {\n' + - ' destValue = (destValue = result[index]) && isArr\n' + - ' ? (isArray(destValue) ? destValue : [])\n' + - ' : (isPlainObject(destValue) ? destValue : {});\n' + - ' stack.push({ value: destValue, source: value });\n' + - ' result[index] = callee(destValue, value, isPlainObject, stack)\n' + + ' stackA.push(source);\n' + + ' stackB.push(value = (value = result[index]) && isArr\n' + + ' ? (isArray(value) ? value : [])\n' + + ' : (isPlainObject(value) ? value : {})\n' + + ' );\n' + + ' result[index] = callee(value, source, compareAscending, stackA, stackB)\n' + ' }\n' + - '} else if (value != null) {\n' + - ' result[index] = value\n' + + '} else if (source != null) {\n' + + ' result[index] = source\n' + '}' }); /** - * Creates a shallow clone of `object` composed of the specified properties. + * Creates a shallow clone of `object` excluding the specified properties. * Property names may be specified as individual arguments or as arrays of - * property names. + * property names. If `callback` is passed, it will be executed for each property + * in the `object`, omitting the properties `callback` returns truthy for. The + * `callback` is bound to `thisArg` and invoked with three arguments; (value, key, object). * * @static * @memberOf _ * @category Objects * @param {Object} object The source object. - * @param {Object} [prop1, prop2, ...] The properties to pick. - * @returns {Object} Returns an object composed of the picked properties. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to omit + * or the function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object without the omitted properties. * @example * - * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age'); + * _.omit({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid'); * // => { 'name': 'moe', 'age': 40 } + * + * _.omit({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * return key.charAt(0) == '_'; + * }); + * // => { 'name': 'moe' } */ - function pick(object) { - var result = {}; - if (!object) { - return result; - } - var prop, - index = 0, - props = concat.apply(ArrayProto, arguments), - length = props.length; - - // start `index` at `1` to skip `object` - while (++index < length) { - prop = props[index]; - if (prop in object) { - result[prop] = object[prop]; - } - } - return result; - } + var omit = createIterator(omitIteratorOptions); /** - * Gets the size of `value` by returning `value.length` if `value` is an - * array, string, or `arguments` object. If `value` is an object, size is - * determined by returning the number of own enumerable properties it has. + * Creates a two dimensional array of the given object's key-value pairs, + * i.e. `[[key1, value1], [key2, value2]]`. * - * @deprecated * @static * @memberOf _ * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Number} Returns `value.length` or number of own enumerable properties. + * @param {Object} object The object to inspect. + * @returns {Array} Returns new array of key-value pairs. * @example * - * _.size([1, 2]); - * // => 2 + * _.pairs({ 'moe': 30, 'larry': 40, 'curly': 50 }); + * // => [['moe', 30], ['larry', 40], ['curly', 50]] (order is not guaranteed) + */ + var pairs = createIterator({ + 'args': 'object', + 'init':'[]', + 'inLoop': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '([index, value])' + }); + + /** + * Creates a shallow clone of `object` composed of the specified properties. + * Property names may be specified as individual arguments or as arrays of + * property names. If `callback` is passed, it will be executed for each property + * in the `object`, picking the properties `callback` returns truthy for. The + * `callback` is bound to `thisArg` and invoked with three arguments; (value, key, object). * - * _.size({ 'one': 1, 'two': 2, 'three': 3 }); - * // => 3 + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to pick + * or the function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object composed of the picked properties. + * @example * - * _.size('curly'); - * // => 5 + * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age'); + * // => { 'name': 'moe', 'age': 40 } + * + * _.pick({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * return key.charAt(0) != '_'; + * }); + * // => { 'name': 'moe' } */ - function size(value) { - if (!value) { - return 0; - } - var className = toString.call(value), - length = value.length; - - // return `value.length` for `arguments` objects, arrays, strings, and DOM - // query collections of libraries like jQuery and MooTools - // http://code.google.com/p/fbug/source/browse/branches/firebug1.9/content/firebug/chrome/reps.js?r=12614#653 - // http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/InjectedScriptSource.js?rev=125186#L609 - if (arrayLikeClasses[className] || (noArgsClass && isArguments(value)) || - (className == objectClass && length > -1 && length === length >>> 0 && isFunction(value.splice))) { - return length; - } - return keys(value).length; - } + var pick = createIterator(omitIteratorOptions, { + 'top': + 'if (typeof callback != \'function\') {\n' + + ' var prop,\n' + + ' props = concat.apply(ArrayProto, arguments),\n' + + ' length = props.length;\n' + + ' for (index = 1; index < length; index++) {\n' + + ' prop = props[index];\n' + + ' if (prop in object) result[prop] = object[prop]\n' + + ' }\n' + + '} else {\n' + + ' callback = createCallback(callback, thisArg)', + 'inLoop': + 'if (callback(value, index, object)) result[index] = value', + 'bottom': '}' + }); /** * Creates an array composed of the own enumerable property values of `object`. @@ -1876,7 +1895,7 @@ 'init': 'false', 'noCharByIndex': false, 'beforeLoop': { - 'array': 'if (toString.call(iteratee) == stringClass) return collection.indexOf(target) > -1' + 'array': 'if (toString.call(collection) == stringClass) return collection.indexOf(target) > -1' }, 'inLoop': 'if (value === target) return true' }); @@ -1885,16 +1904,16 @@ * Creates an object composed of keys returned from running each element of * `collection` through a `callback`. The corresponding value of each key is * the number of times the key was returned by `callback`. The `callback` is - * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * bound to `thisArg` and invoked with three arguments; (value, index|key, collection). * The `callback` argument may also be the name of a property to count by (e.g. 'length'). * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|String} callback The function called per iteration or - * property name to count by. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Function|String} callback|property The function called per iteration + * or property name to count by. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the composed aggregate object. * @example * @@ -1911,7 +1930,7 @@ /** * Checks if the `callback` returns a truthy value for **all** elements of a - * `collection`. The `callback` is bound to `thisArg` and invoked with 3 + * `collection`. The `callback` is bound to `thisArg` and invoked with three * arguments; (value, index|key, collection). * * @static @@ -1920,8 +1939,9 @@ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Boolean} Returns `true` if all elements pass the callback check, else `false`. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Boolean} Returns `true` if all elements pass the callback check, + * else `false`. * @example * * _.every([true, 1, null, 'yes'], Boolean); @@ -1932,7 +1952,7 @@ /** * Examines each element in a `collection`, returning an array of all elements * the `callback` returns truthy for. The `callback` is bound to `thisArg` and - * invoked with 3 arguments; (value, index|key, collection). + * invoked with three arguments; (value, index|key, collection). * * @static * @memberOf _ @@ -1940,8 +1960,8 @@ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of elements that passed callback check. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that passed the callback check. * @example * * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); @@ -1953,7 +1973,7 @@ * Examines each element in a `collection`, returning the first one the `callback` * returns truthy for. The function returns as soon as it finds an acceptable * element, and does not iterate over the entire `collection`. The `callback` is - * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * bound to `thisArg` and invoked with three arguments; (value, index|key, collection). * * @static * @memberOf _ @@ -1961,8 +1981,9 @@ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the element that passed the callback check, else `undefined`. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the element that passed the callback check, + * else `undefined`. * @example * * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); @@ -1975,9 +1996,9 @@ /** * Iterates over a `collection`, executing the `callback` for each element in - * the `collection`. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; (value, index|key, collection). Callbacks may exit iteration - * early by explicitly returning `false`. + * the `collection`. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). Callbacks may exit iteration early + * by explicitly returning `false`. * * @static * @memberOf _ @@ -1985,8 +2006,8 @@ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array|Object} Returns `collection`. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|String} Returns `collection`. * @example * * _([1, 2, 3]).forEach(alert).join(','); @@ -2001,16 +2022,16 @@ * Creates an object composed of keys returned from running each element of * `collection` through a `callback`. The corresponding value of each key is an * array of elements passed to `callback` that returned the key. The `callback` - * is bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). * The `callback` argument may also be the name of a property to count by (e.g. 'length'). * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|String} callback The function called per iteration or - * property name to group by. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Function|String} callback|property The function called per iteration + * or property name to group by. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the composed aggregate object. * @example * @@ -2025,15 +2046,15 @@ */ var groupBy = createIterator(baseIteratorOptions, countByIteratorOptions, { 'inLoop': - 'prop = callback(value, index, collection);\n' + + 'var prop = callback(value, index, collection);\n' + '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(value)' }); /** - * Invokes the method named by `methodName` on each element in the `collection`. - * Additional arguments will be passed to each invoked method. If `methodName` - * is a function it will be invoked for, and `this` bound to, each element - * in the `collection`. + * Invokes the method named by `methodName` on each element in the `collection`, + * returning an array of the results of each invoked method. Additional arguments + * will be passed to each invoked method. If `methodName` is a function it will + * be invoked for, and `this` bound to, each element in the `collection`. * * @static * @memberOf _ @@ -2042,7 +2063,7 @@ * @param {Function|String} methodName The name of the method to invoke or * the function invoked per iteration. * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. - * @returns {Array} Returns a new array of values returned from each invoked method. + * @returns {Array} Returns a new array of the results of each invoked method. * @example * * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); @@ -2066,9 +2087,9 @@ }); /** - * Creates a new array of values by running each element in the `collection` + * Creates an array of values by running each element in the `collection` * through a `callback`. The `callback` is bound to `thisArg` and invoked with - * 3 arguments; (value, index|key, collection). + * three arguments; (value, index|key, collection). * * @static * @memberOf _ @@ -2076,8 +2097,8 @@ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of elements returned by the callback. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of the results of each `callback` execution. * @example * * _.map([1, 2, 3], function(num) { return num * 3; }); @@ -2130,7 +2151,7 @@ * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} [accumulator] Initial value of the accumulator. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the accumulated value. * @example * @@ -2142,9 +2163,9 @@ 'init': 'accumulator', 'top': 'var noaccum = arguments.length < 3;\n' + - 'if (thisArg) callback = iteratorBind(callback, thisArg)', + 'callback = createCallback(callback, thisArg)', 'beforeLoop': { - 'array': 'if (noaccum) result = collection[++index]' + 'array': 'if (noaccum) result = iteratee[++index]' }, 'inLoop': { 'array': @@ -2166,7 +2187,7 @@ * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} [accumulator] Initial value of the accumulator. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the accumulated value. * @example * @@ -2175,42 +2196,22 @@ * // => [4, 5, 2, 3, 0, 1] */ function reduceRight(collection, callback, accumulator, thisArg) { - if (!collection) { - return accumulator; - } - - var length = collection.length, + var iteratee = collection, + length = collection.length, noaccum = arguments.length < 3; - if(thisArg) { - callback = iteratorBind(callback, thisArg); - } - // Opera 10.53-10.60 JITted `length >>> 0` returns the wrong value for negative numbers - if (length > -1 && length === length >>> 0) { - var iteratee = noCharByIndex && toString.call(collection) == stringClass - ? collection.split('') - : collection; - - if (length && noaccum) { - accumulator = iteratee[--length]; - } - while (length--) { - accumulator = callback(accumulator, iteratee[length], length, collection); - } - return accumulator; - } - - var prop, - props = keys(collection); - - length = props.length; - if (length && noaccum) { - accumulator = collection[props[--length]]; - } - while (length--) { - prop = props[length]; - accumulator = callback(accumulator, collection[prop], prop, collection); + if (length !== +length) { + var props = keys(collection); + length = props.length; + } else if (noCharByIndex && toString.call(collection) == stringClass) { + iteratee = collection.split(''); } + forEach(collection, function(value, index, object) { + index = props ? props[--length] : --length; + accumulator = noaccum + ? (noaccum = false, iteratee[index]) + : callback.call(thisArg, accumulator, iteratee[index], index, object); + }); return accumulator; } @@ -2223,8 +2224,9 @@ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of elements that did **not** pass the callback check. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that did **not** pass the + * callback check. * @example * * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); @@ -2235,10 +2237,35 @@ }); /** + * Gets the size of the `collection` by returning `collection.length` for arrays + * and array-like objects or the number of own enumerable properties for objects. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to inspect. + * @returns {Number} Returns `collection.length` or number of own enumerable properties. + * @example + * + * _.size([1, 2]); + * // => 2 + * + * _.size({ 'one': 1, 'two': 2, 'three': 3 }); + * // => 3 + * + * _.size('curly'); + * // => 5 + */ + function size(collection) { + var length = collection ? collection.length : 0; + return length === +length ? length : keys(collection).length; + } + + /** * Checks if the `callback` returns a truthy value for **any** element of a * `collection`. The function returns as soon as it finds passing value, and * does not iterate over the entire `collection`. The `callback` is bound to - * `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * `thisArg` and invoked with three arguments; (value, index|key, collection). * * @static * @memberOf _ @@ -2246,8 +2273,9 @@ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Boolean} Returns `true` if any element passes the callback check, else `false`. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Boolean} Returns `true` if any element passes the callback check, + * else `false`. * @example * * _.some([null, 0, 'yes', false]); @@ -2259,18 +2287,18 @@ }); /** - * Creates a new array, stable sorted in ascending order by the results of + * Creates an array, stable sorted in ascending order by the results of * running each element of `collection` through a `callback`. The `callback` - * is bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). * The `callback` argument may also be the name of a property to sort by (e.g. 'length'). * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|String} callback The function called per iteration or - * property name to sort by. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Function|String} callback|property The function called per iteration + * or property name to sort by. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a new array of sorted elements. * @example * @@ -2307,8 +2335,7 @@ }); /** - * Converts the `collection`, to an array. Useful for converting the - * `arguments` object. + * Converts the `collection`, to an array. * * @static * @memberOf _ @@ -2321,14 +2348,8 @@ * // => [2, 3, 4] */ function toArray(collection) { - if (!collection) { - return []; - } - if (collection.toArray && isFunction(collection.toArray)) { - return collection.toArray(); - } - var length = collection.length; - if (length > -1 && length === length >>> 0) { + var length = collection ? collection.length : 0; + if (length === +length) { return (noArraySliceOnStrings ? toString.call(collection) == stringClass : typeof collection == 'string') ? collection.split('') : slice.call(collection); @@ -2360,21 +2381,21 @@ var where = createIterator(filterIteratorOptions, { 'args': 'collection, properties', 'top': - 'var pass, prop, propIndex, props = [];\n' + + 'var props = [];\n' + 'forIn(properties, function(value, prop) { props.push(prop) });\n' + 'var propsLength = props.length', 'inLoop': - 'for (pass = true, propIndex = 0; propIndex < propsLength; propIndex++) {\n' + + 'for (var prop, pass = true, propIndex = 0; propIndex < propsLength; propIndex++) {\n' + ' prop = props[propIndex];\n' + ' if (!(pass = value[prop] === properties[prop])) break\n' + '}\n' + - 'if (pass) result.push(value)' + 'pass && result.push(value)' }); /*--------------------------------------------------------------------------*/ /** - * Creates a new array with all falsey values of `array` removed. The values + * Creates an array with all falsey values of `array` removed. The values * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. * * @static @@ -2388,12 +2409,9 @@ * // => [1, 2, 3] */ function compact(array) { - var result = []; - if (!array) { - return result; - } var index = -1, - length = array.length; + length = array.length, + result = []; while (++index < length) { if (array[index]) { @@ -2404,7 +2422,7 @@ } /** - * Creates a new array of `array` elements not present in the other arrays + * Creates an array of `array` elements not present in the other arrays * using strict equality for comparisons, i.e. `===`. * * @static @@ -2420,14 +2438,11 @@ * // => [1, 3, 4] */ function difference(array) { - var result = []; - if (!array) { - return result; - } var index = -1, length = array.length, - flattened = concat.apply(result, arguments), - contains = cachedContains(flattened, length); + flattened = concat.apply(ArrayProto, arguments), + contains = cachedContains(flattened, length), + result = []; while (++index < length) { if (!contains(array[index])) { @@ -2447,7 +2462,7 @@ * @category Arrays * @param {Array} array The array to query. * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with + * @param- {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `n`. * @returns {Mixed} Returns the first element or an array of the first `n` * elements of `array`. @@ -2457,9 +2472,7 @@ * // => 5 */ function first(array, n, guard) { - if (array) { - return (n == null || guard) ? array[0] : slice.call(array, 0, n); - } + return (n == null || guard) ? array[0] : slice.call(array, 0, n); } /** @@ -2481,16 +2494,15 @@ * // => [1, 2, 3, [[4]]]; */ function flatten(array, shallow) { - var result = []; - if (!array) { - return result; - } var value, index = -1, - length = array.length; + length = array.length, + result = []; while (++index < length) { value = array[index]; + + // recursively flatten arrays (susceptible to call stack limits) if (isArray(value)) { push.apply(result, shallow ? value : flatten(value)); } else { @@ -2525,15 +2537,12 @@ * // => 2 */ function indexOf(array, value, fromIndex) { - if (!array) { - return -1; - } var index = -1, length = array.length; if (fromIndex) { if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1; + index = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) - 1; } else { index = sortedIndex(array, value); return array[index] === value ? index : -1; @@ -2556,7 +2565,7 @@ * @category Arrays * @param {Array} array The array to query. * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with + * @param- {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `n`. * @returns {Array} Returns all but the last element or `n` elements of `array`. * @example @@ -2565,9 +2574,6 @@ * // => [3, 2] */ function initial(array, n, guard) { - if (!array) { - return []; - } return slice.call(array, 0, -((n == null || guard) ? 1 : n)); } @@ -2587,22 +2593,21 @@ * // => [1, 2] */ function intersection(array) { - var result = []; - if (!array) { - return result; - } var value, + argsLength = arguments.length, + cache = [], index = -1, length = array.length, - others = slice.call(arguments, 1), - cache = []; + result = []; - while (++index < length) { + array: while (++index < length) { value = array[index]; - if (indexOf(result, value) < 0 && - every(others, function(other, index) { - return (cache[index] || (cache[index] = cachedContains(other)))(value); - })) { + if (indexOf(result, value) < 0) { + for (var argsIndex = 1; argsIndex < argsLength; argsIndex++) { + if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(arguments[argsIndex])))(value)) { + continue array; + } + } result.push(value); } } @@ -2610,15 +2615,15 @@ } /** - * Gets the last element of the `array`. Pass `n` to return the lasy `n` - * elementsvof the `array`. + * Gets the last element of the `array`. Pass `n` to return the last `n` + * elements of the `array`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to query. * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with + * @param- {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `n`. * @returns {Mixed} Returns the last element or an array of the last `n` * elements of `array`. @@ -2628,10 +2633,8 @@ * // => 1 */ function last(array, n, guard) { - if (array) { - var length = array.length; - return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); - } + var length = array.length; + return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); } /** @@ -2654,12 +2657,9 @@ * // => 1 */ function lastIndexOf(array, value, fromIndex) { - if (!array) { - return -1; - } var index = array.length; if (fromIndex && typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1; + index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; } while (index--) { if (array[index] === value) { @@ -2673,14 +2673,14 @@ * Retrieves the maximum value of an `array`. If `callback` is passed, * it will be executed for each value in the `array` to generate the * criterion by which the value is ranked. The `callback` is bound to - * `thisArg` and invoked with 3 arguments; (value, index, array). + * `thisArg` and invoked with three arguments; (value, index, array). * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to iterate over. * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the maximum value. * @example * @@ -2694,27 +2694,13 @@ * // => { 'name': 'curly', 'age': 60 }; */ function max(array, callback, thisArg) { - var computed = -Infinity, - result = computed; - - if (!array) { - return result; - } var current, + computed = -Infinity, index = -1, - length = array.length; + length = array ? array.length : 0, + result = computed; - if (!callback) { - while (++index < length) { - if (array[index] > result) { - result = array[index]; - } - } - return result; - } - if (thisArg) { - callback = iteratorBind(callback, thisArg); - } + callback = createCallback(callback, thisArg); while (++index < length) { current = callback(array[index], index, array); if (current > computed) { @@ -2729,14 +2715,14 @@ * Retrieves the minimum value of an `array`. If `callback` is passed, * it will be executed for each value in the `array` to generate the * criterion by which the value is ranked. The `callback` is bound to `thisArg` - * and invoked with 3 arguments; (value, index, array). + * and invoked with three arguments; (value, index, array). * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to iterate over. * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the minimum value. * @example * @@ -2744,27 +2730,13 @@ * // => 2 */ function min(array, callback, thisArg) { - var computed = Infinity, - result = computed; - - if (!array) { - return result; - } var current, + computed = Infinity, index = -1, - length = array.length; + length = array ? array.length : 0, + result = computed; - if (!callback) { - while (++index < length) { - if (array[index] < result) { - result = array[index]; - } - } - return result; - } - if (thisArg) { - callback = iteratorBind(callback, thisArg); - } + callback = createCallback(callback, thisArg); while (++index < length) { current = callback(array[index], index, array); if (current < computed) { @@ -2776,6 +2748,38 @@ } /** + * Creates an object composed from arrays of `keys` and `values`. Pass either + * a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`, or + * two arrays, one of `keys` and one of corresponding `values`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} keys The array of keys. + * @param {Array} [values=[]] The array of values. + * @returns {Object} Returns an object composed of the given keys and + * corresponding values. + * @example + * + * _.object(['moe', 'larry', 'curly'], [30, 40, 50]); + * // => { 'moe': 30, 'larry': 40, 'curly': 50 } + */ + function object(keys, values) { + var index = -1, + length = keys.length, + result = {}; + + while (++index < length) { + if (values) { + result[keys[index]] = values[index]; + } else { + result[keys[index][0]] = keys[index][1]; + } + } + return result; + } + + /** * Creates an array of numbers (positive and/or negative) progressing from * `start` up to but not including `stop`. This method is a port of Python's * `range()` function. See http://docs.python.org/library/functions.html#range. @@ -2815,7 +2819,7 @@ // use `Array(length)` so V8 will avoid the slower "dictionary" mode // http://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s var index = -1, - length = Math.max(0, Math.ceil((end - start) / step)), + length = nativeMax(0, Math.ceil((end - start) / step)), result = Array(length); while (++index < length) { @@ -2831,11 +2835,11 @@ * * @static * @memberOf _ - * @alias tail + * @alias drop, tail * @category Arrays * @param {Array} array The array to query. * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with + * @param- {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `n`. * @returns {Array} Returns all but the first value or `n` values of `array`. * @example @@ -2844,14 +2848,11 @@ * // => [2, 1] */ function rest(array, n, guard) { - if (!array) { - return []; - } return slice.call(array, (n == null || guard) ? 1 : n); } /** - * Creates a new array of shuffled `array` values, using a version of the + * Creates an array of shuffled `array` values, using a version of the * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. * * @static @@ -2865,16 +2866,13 @@ * // => [4, 1, 6, 3, 5, 2] */ function shuffle(array) { - if (!array) { - return []; - } var rand, index = -1, length = array.length, result = Array(length); while (++index < length) { - rand = Math.floor(Math.random() * (index + 1)); + rand = nativeFloor(nativeRandom() * (index + 1)); result[index] = result[rand]; result[rand] = array[index]; } @@ -2886,58 +2884,51 @@ * should be inserted into `array` in order to maintain the sort order of the * sorted `array`. If `callback` is passed, it will be executed for `value` and * each element in `array` to compute their sort ranking. The `callback` is - * bound to `thisArg` and invoked with 1 argument; (value). + * bound to `thisArg` and invoked with one argument; (value). The `callback` + * argument may also be the name of a property to order by. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to iterate over. * @param {Mixed} value The value to evaluate. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Function|String} [callback=identity|property] The function called + * per iteration or property name to order by. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Number} Returns the index at which the value should be inserted * into `array`. * @example * - * _.sortedIndex([20, 30, 40], 35); + * _.sortedIndex([20, 30, 50], 40); + * // => 2 + * + * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x'); * // => 2 * * var dict = { - * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 } + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 } * }; * - * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { * return dict.wordToNumber[word]; * }); * // => 2 * - * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { * return this.wordToNumber[word]; * }, dict); * // => 2 */ function sortedIndex(array, value, callback, thisArg) { - if (!array) { - return 0; - } var mid, low = 0, high = array.length; - if (callback) { - if (thisArg) { - callback = bind(callback, thisArg); - } - value = callback(value); - while (low < high) { - mid = (low + high) >>> 1; - callback(array[mid]) < value ? low = mid + 1 : high = mid; - } - } else { - while (low < high) { - mid = (low + high) >>> 1; - array[mid] < value ? low = mid + 1 : high = mid; - } + callback = createCallback(callback, thisArg); + value = callback(value); + while (low < high) { + mid = (low + high) >>> 1; + callback(array[mid]) < value ? low = mid + 1 : high = mid; } return low; } @@ -2959,9 +2950,9 @@ */ function union() { var index = -1, - result = [], - flattened = concat.apply(result, arguments), - length = flattened.length; + flattened = concat.apply(ArrayProto, arguments), + length = flattened.length, + result = []; while (++index < length) { if (indexOf(result, flattened[index]) < 0) { @@ -2976,7 +2967,7 @@ * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true` * for `isSorted` will run a faster algorithm. If `callback` is passed, each * element of `array` is passed through a callback` before uniqueness is computed. - * The `callback` is bound to `thisArg` and invoked with 3 arguments; (value, index, array). + * The `callback` is bound to `thisArg` and invoked with three arguments; (value, index, array). * * @static * @memberOf _ @@ -2985,7 +2976,7 @@ * @param {Array} array The array to process. * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted. * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a duplicate-value-free array. * @example * @@ -3002,13 +2993,10 @@ * // => [1, 2, 3] */ function uniq(array, isSorted, callback, thisArg) { - var result = []; - if (!array) { - return result; - } var computed, index = -1, length = array.length, + result = [], seen = []; // juggle arguments @@ -3017,11 +3005,7 @@ callback = isSorted; isSorted = false; } - if (!callback) { - callback = identity; - } else if (thisArg) { - callback = iteratorBind(callback, thisArg); - } + callback = createCallback(callback, thisArg); while (++index < length) { computed = callback(array[index], index, array); if (isSorted @@ -3036,7 +3020,7 @@ } /** - * Creates a new array with all occurrences of the passed values removed using + * Creates an array with all occurrences of the passed values removed using * strict equality for comparisons, i.e. `===`. * * @static @@ -3051,13 +3035,10 @@ * // => [2, 3, 4] */ function without(array) { - var result = []; - if (!array) { - return result; - } var index = -1, length = array.length, - contains = cachedContains(arguments, 1, 20); + contains = cachedContains(arguments, 1, 20), + result = []; while (++index < length) { if (!contains(array[index])) { @@ -3084,9 +3065,6 @@ * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]] */ function zip(array) { - if (!array) { - return []; - } var index = -1, length = max(pluck(arguments, 'length')), result = Array(length); @@ -3097,40 +3075,10 @@ return result; } - /** - * Creates an object composed from an array of `keys` and an array of `values`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} keys The array of keys. - * @param {Array} [values=[]] The array of values. - * @returns {Object} Returns an object composed of the given keys and - * corresponding values. - * @example - * - * _.zipObject(['moe', 'larry', 'curly'], [30, 40, 50]); - * // => { 'moe': 30, 'larry': 40, 'curly': 50 } - */ - function zipObject(keys, values) { - if (!keys) { - return {}; - } - var index = -1, - length = keys.length, - result = {}; - - values || (values = []); - while (++index < length) { - result[keys[index]] = values[index]; - } - return result; - } - /*--------------------------------------------------------------------------*/ /** - * Creates a new function that is restricted to executing only after it is + * Creates a function that is restricted to executing only after it is * called `n` times. * * @static @@ -3160,21 +3108,19 @@ } /** - * Creates a new function that, when called, invokes `func` with the `this` + * Creates a function that, when called, invokes `func` with the `this` * binding of `thisArg` and prepends any additional `bind` arguments to those - * passed to the bound function. Lazy defined methods may be bound by passing - * the object they are bound to as `func` and the method name as `thisArg`. + * passed to the bound function. * * @static * @memberOf _ * @category Functions - * @param {Function|Object} func The function to bind or the object the method belongs to. - * @param {Mixed} [thisArg] The `this` binding of `func` or the method name. + * @param {Function} func The function to bind. + * @param {Mixed} [thisArg] The `this` binding of `func`. * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. * @returns {Function} Returns the new bound function. * @example * - * // basic bind * var func = function(greeting) { * return greeting + ' ' + this.name; * }; @@ -3182,72 +3128,13 @@ * func = _.bind(func, { 'name': 'moe' }, 'hi'); * func(); * // => 'hi moe' - * - * // lazy bind - * var object = { - * 'name': 'moe', - * 'greet': function(greeting) { - * return greeting + ' ' + this.name; - * } - * }; - * - * var func = _.bind(object, 'greet', 'hi'); - * func(); - * // => 'hi moe' - * - * object.greet = function(greeting) { - * return greeting + ', ' + this.name + '!'; - * }; - * - * func(); - * // => 'hi, moe!' */ function bind(func, thisArg) { - var methodName, - isFunc = isFunction(func); - - // juggle arguments - if (!isFunc) { - methodName = thisArg; - thisArg = func; - } // use `Function#bind` if it exists and is fast // (in V8 `Function#bind` is slower except when partially applied) - else if (isBindFast || (nativeBind && arguments.length > 2)) { - return nativeBind.call.apply(nativeBind, arguments); - } - - var partialArgs = slice.call(arguments, 2); - - function bound() { - // `Function#bind` spec - // http://es5.github.com/#x15.3.4.5 - var args = arguments, - thisBinding = thisArg; - - if (!isFunc) { - func = thisArg[methodName]; - } - if (partialArgs.length) { - args = args.length - ? partialArgs.concat(slice.call(args)) - : partialArgs; - } - if (this instanceof bound) { - // get `func` instance if `bound` is invoked in a `new` expression - noop.prototype = func.prototype; - thisBinding = new noop; - - // mimic the constructor's `return` behavior - // http://es5.github.com/#x13.2.2 - var result = func.apply(thisBinding, args); - return result && objectTypes[typeof result] - ? result - : thisBinding - } - return func.apply(thisBinding, args); - } - return bound; + return isBindFast || (nativeBind && arguments.length > 2) + ? nativeBind.call.apply(nativeBind, arguments) + : createBound(func, thisArg, slice.call(arguments, 2)); } /** @@ -3276,7 +3163,6 @@ 'useHas': false, 'useStrict': false, 'args': 'object', - 'init': 'object', 'top': 'var funcs = arguments,\n' + ' length = funcs.length;\n' + @@ -3293,7 +3179,7 @@ }); /** - * Creates a new function that is the composition of the passed functions, + * Creates a function that is the composition of the passed functions, * where each function consumes the return value of the function that follows. * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. * @@ -3324,7 +3210,7 @@ } /** - * Creates a new function that will delay the execution of `func` until after + * Creates a function that will delay the execution of `func` until after * `wait` milliseconds have elapsed since the last time it was invoked. Pass * `true` for `immediate` to cause debounce to invoke `func` on the leading, * instead of the trailing, edge of the `wait` timeout. Subsequent calls to @@ -3352,7 +3238,7 @@ function delayed() { timeoutId = null; if (!immediate) { - func.apply(thisArg, args); + result = func.apply(thisArg, args); } } @@ -3414,7 +3300,43 @@ } /** - * Creates a new function that memoizes the result of `func`. If `resolver` is + * Creates a function that, when called, invokes `object[methodName]` and + * prepends any additional `lateBind` arguments to those passed to the bound + * function. This method + * + * @static + * @memberOf _ + * @category Functions + * @param {Object} object The object the method belongs to. + * @param {String} methodName The method name. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var object = { + * 'name': 'moe', + * 'greet': function(greeting) { + * return greeting + ' ' + this.name; + * } + * }; + * + * var func = _.bind(object, 'greet', 'hi'); + * func(); + * // => 'hi moe' + * + * object.greet = function(greeting) { + * return greeting + ', ' + this.name + '!'; + * }; + * + * func(); + * // => 'hi, moe!' + */ + function lateBind(object, methodName) { + return createBound(methodName, object, slice.call(arguments, 2)); + } + + /** + * Creates a function that memoizes the result of `func`. If `resolver` is * passed, it will be used to determine the cache key for storing the result * based on the arguments passed to the memoized function. By default, the first * argument passed to the memoized function is used as the cache key. @@ -3442,7 +3364,7 @@ } /** - * Creates a new function that is restricted to one execution. Repeat calls to + * Creates a function that is restricted to one execution. Repeat calls to * the function will return the value of the first call. * * @static @@ -3475,9 +3397,9 @@ } /** - * Creates a new function that, when called, invokes `func` with any additional + * Creates a function that, when called, invokes `func` with any additional * `partial` arguments prepended to those passed to the new function. This method - * is similar `bind`, except it does **not** alter the `this` binding. + * is similar to `bind`, except it does **not** alter the `this` binding. * * @static * @memberOf _ @@ -3493,25 +3415,11 @@ * // => 'hi: moe' */ function partial(func) { - var args = slice.call(arguments, 1), - argsLength = args.length; - - return function() { - var result, - others = arguments; - - if (others.length) { - args.length = argsLength; - push.apply(args, others); - } - result = args.length == 1 ? func.call(this, args[0]) : func.apply(this, args); - args.length = argsLength; - return result; - }; + return createBound(func, slice.call(arguments, 1)); } /** - * Creates a new function that, when executed, will only call the `func` + * Creates a function that, when executed, will only call the `func` * function at most once per every `wait` milliseconds. If the throttled * function is invoked more than once during the `wait` timeout, `func` will * also be called on the trailing edge of the timeout. Subsequent calls to the @@ -3538,7 +3446,7 @@ function trailingCall() { lastCalled = new Date; timeoutId = null; - func.apply(thisArg, args); + result = func.apply(thisArg, args); } return function() { @@ -3560,7 +3468,7 @@ } /** - * Creates a new function that passes `value` to the `wrapper` function as its + * Creates a function that passes `value` to the `wrapper` function as its * first argument. Additional arguments passed to the new function are appended * to those passed to the `wrapper` function. * @@ -3592,8 +3500,8 @@ /*--------------------------------------------------------------------------*/ /** - * Escapes a string for inclusion in HTML, replacing `&`, `<`, `"`, and `'` - * characters. + * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their + * corresponding HTML entities. * * @static * @memberOf _ @@ -3655,15 +3563,15 @@ forEach(functions(object), function(methodName) { var func = lodash[methodName] = object[methodName]; - LoDash.prototype[methodName] = function() { - var args = [this._wrapped]; + lodash.prototype[methodName] = function() { + var args = [this.__wrapped__]; if (arguments.length) { push.apply(args, arguments); } var result = func.apply(lodash, args); - if (this._chain) { - result = new LoDash(result); - result._chain = true; + if (this.__chain__) { + result = new lodash(result); + result.__chain__ = true; } return result; }; @@ -3688,6 +3596,36 @@ } /** + * Produces a random number between `min` and `max` (inclusive). If only one + * argument is passed, a number between `0` and the given number will be returned. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Number} [min=0] The minimum possible value. + * @param {Number} [max=1] The maximum possible value. + * @returns {Number} Returns a random number. + * @example + * + * _.random(0, 5); + * // => a number between 1 and 5 + * + * _.random(5); + * // => also a number between 1 and 5 + */ + function random(min, max) { + if (min == null && max == null) { + max = 1; + } + min = +min || 0; + if (max == null) { + max = min; + min = 0; + } + return min + nativeFloor(nativeRandom() * ((+max || 0) - min + 1)); + } + + /** * Resolves the value of `property` on `object`. If `property` is a function * it will be invoked and its result returned, else the property value is * returned. If `object` is falsey, then `null` is returned. @@ -3697,7 +3635,7 @@ * @memberOf _ * @category Utilities * @param {Object} object The object to inspect. - * @param {String} property The property to get the result of. + * @param {String} property The property to get the value of. * @returns {Mixed} Returns the resolved value. * @example * @@ -3717,10 +3655,7 @@ function result(object, property) { // based on Backbone's private `getValue` function // https://github.com/documentcloud/backbone/blob/0.9.2/backbone.js#L1419-1424 - if (!object) { - return null; - } - var value = object[property]; + var value = object ? object[property] : null; return isFunction(value) ? object[property]() : value; } @@ -3750,7 +3685,7 @@ * compiled({ 'name': 'moe' }); * // => 'hello: moe' * - * var list = '<% _.forEach(people, function(name) { %> <li><%= name %></li> <% }); %>'; + * var list = '<% _.forEach(people, function(name) { %><li><%= name %></li><% }); %>'; * _.template(list, { 'people': ['moe', 'larry', 'curly'] }); * // => '<li>moe</li><li>larry</li><li>curly</li>' * @@ -3759,12 +3694,12 @@ * // => '<b><script></b>' * * // using the internal `print` function in "evaluate" delimiters - * _.template('<% print("Hello " + epithet); %>', { 'epithet': 'stooge' }); + * _.template('<% print("Hello " + epithet); %>.', { 'epithet': 'stooge' }); * // => 'Hello stooge.' * * // using custom template delimiter settings * _.templateSettings = { - * 'interpolate': /\{\{(.+?)\}\}/g + * 'interpolate': /\{\{([\s\S]+?)\}\}/g * }; * * _.template('Hello {{ name }}!', { 'name': 'Mustache' }); @@ -3793,89 +3728,62 @@ // and Laura Doktorova's doT.js // https://github.com/olado/doT options || (options = {}); - text += ''; var isEvaluating, result, - escapeDelimiter = options.escape, - evaluateDelimiter = options.evaluate, - interpolateDelimiter = options.interpolate, + index = 0, settings = lodash.templateSettings, + source = "__p += '", variable = options.variable || settings.variable, hasVariable = variable; - // use default settings if no options object is provided - if (escapeDelimiter == null) { - escapeDelimiter = settings.escape; - } - if (evaluateDelimiter == null) { - // use `false` as the fallback value, instead of leaving it `undefined`, - // so the initial assignment of `reEvaluateDelimiter` will still occur - evaluateDelimiter = settings.evaluate || false; - } - if (interpolateDelimiter == null) { - interpolateDelimiter = settings.interpolate; - } - - // tokenize delimiters to avoid escaping them - if (escapeDelimiter) { - text = text.replace(escapeDelimiter, tokenizeEscape); - } - if (interpolateDelimiter) { - text = text.replace(interpolateDelimiter, tokenizeInterpolate); - } - if (evaluateDelimiter != lastEvaluateDelimiter) { - // generate `reEvaluateDelimiter` to match `_.templateSettings.evaluate` - // and internal `<e%- %>`, `<e%= %>` delimiters - lastEvaluateDelimiter = evaluateDelimiter; - reEvaluateDelimiter = RegExp( - '<e%-([\\s\\S]+?)%>|<e%=([\\s\\S]+?)%>' + - (evaluateDelimiter ? '|' + evaluateDelimiter.source : '') - , 'g'); - } - isEvaluating = tokenized.length; - text = text.replace(reEvaluateDelimiter, tokenizeEvaluate); - isEvaluating = isEvaluating != tokenized.length; - - // escape characters that cannot be included in string literals and - // detokenize delimiter code snippets - text = "__p += '" + text - .replace(reUnescapedString, escapeStringChar) - .replace(reToken, detokenize) + "';\n"; + // compile regexp to match each delimiter + var reDelimiters = RegExp( + (options.escape || settings.escape || reNoMatch).source + '|' + + (options.interpolate || settings.interpolate || reNoMatch).source + '|' + + (options.evaluate || settings.evaluate || reNoMatch).source + '|$' + , 'g'); + + text.replace(reDelimiters, function(match, escapeValue, interpolateValue, evaluateValue, offset) { + // escape characters that cannot be included in string literals + source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar); + + // replace delimiters with snippets + source += + escapeValue ? "' +\n__e(" + escapeValue + ") +\n'" : + evaluateValue ? "';\n" + evaluateValue + ";\n__p += '" : + interpolateValue ? "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'" : ''; + + isEvaluating || (isEvaluating = evaluateValue || reComplexDelimiter.test(escapeValue || interpolateValue)); + index = offset + match.length; + }); - // clear stored code snippets - tokenized.length = 0; + source += "';\n"; // if `variable` is not specified and the template contains "evaluate" // delimiters, wrap a with-statement around the generated code to add the // data object to the top of the scope chain if (!hasVariable) { - variable = lastVariable || 'obj'; - + variable = 'obj'; if (isEvaluating) { - text = 'with (' + variable + ') {\n' + text + '\n}\n'; + source = 'with (' + variable + ') {\n' + source + '\n}\n'; } else { - if (variable != lastVariable) { - // generate `reDoubleVariable` to match references like `obj.obj` inside - // transformed "escape" and "interpolate" delimiters - lastVariable = variable; - reDoubleVariable = RegExp('(\\(\\s*)' + variable + '\\.' + variable + '\\b', 'g'); - } // avoid a with-statement by prepending data object references to property names - text = text + var reDoubleVariable = RegExp('(\\(\\s*)' + variable + '\\.' + variable + '\\b', 'g'); + source = source .replace(reInsertVariable, '$&' + variable + '.') .replace(reDoubleVariable, '$1__d'); } } // cleanup code by stripping empty strings - text = ( isEvaluating ? text.replace(reEmptyStringLeading, '') : text) + source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source) .replace(reEmptyStringMiddle, '$1') .replace(reEmptyStringTrailing, '$1;'); // frame code as the function body - text = 'function(' + variable + ') {\n' + + source = 'function(' + variable + ') {\n' + (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') + 'var __t, __p = \'\', __e = _.escape' + (isEvaluating @@ -3883,22 +3791,20 @@ 'function print() { __p += __j.call(arguments, \'\') }\n' : (hasVariable ? '' : ', __d = ' + variable + '.' + variable + ' || ' + variable) + ';\n' ) + - text + + source + 'return __p\n}'; - // add a sourceURL for easier debugging + // use a sourceURL for easier debugging // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl - if (useSourceURL) { - text += '\n//@ sourceURL=/lodash/template/source[' + (templateCounter++) + ']'; - } + var sourceURL = useSourceURL + ? '\n//@ sourceURL=/lodash/template/source[' + (templateCounter++) + ']' + : ''; try { - result = Function('_', 'return ' + text)(lodash); + result = Function('_', 'return ' + source + sourceURL)(lodash); } catch(e) { - // defer syntax errors until the compiled template is executed to allow - // examining the `source` property beforehand and for consistency, - // because other template related errors occur at execution - result = function() { throw e; }; + e.source = source; + throw e; } if (data) { @@ -3907,39 +3813,60 @@ // provide the compiled function's source via its `toString` method, in // supported environments, or the `source` property as a convenience for // inlining compiled templates during the build process - result.source = text; + result.source = source; return result; } /** - * Executes the `callback` function `n` times. The `callback` is bound to - * `thisArg` and invoked with 1 argument; (index). + * Executes the `callback` function `n` times, returning an array of the results + * of each `callback` execution. The `callback` is bound to `thisArg` and invoked + * with one argument; (index). * * @static * @memberOf _ * @category Utilities * @param {Number} n The number of times to execute the callback. * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of the results of each `callback` execution. * @example * - * _.times(3, function() { genie.grantWish(); }); - * // => calls `genie.grantWish()` 3 times + * var diceRolls = _.times(3, _.partial(_.random, 1, 6)); + * // => [3, 6, 4] * - * _.times(3, function() { this.grantWish(); }, genie); - * // => also calls `genie.grantWish()` 3 times + * _.times(3, function(n) { mage.castSpell(n); }); + * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively + * + * _.times(3, function(n) { this.cast(n); }, mage); + * // => also calls `mage.castSpell(n)` three times */ function times(n, callback, thisArg) { - var index = -1; - if (thisArg) { - while (++index < n) { - callback.call(thisArg, index); - } - } else { - while (++index < n) { - callback(index); - } + n = +n || 0; + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = callback.call(thisArg, index); } + return result; + } + + /** + * Converts the HTML entities `&`, `<`, `>`, `"`, and `'` + * in `string` to their corresponding characters. + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} string The string to unescape. + * @returns {String} Returns the unescaped string. + * @example + * + * _.unescape('Moe, Larry & Curly'); + * // => "Moe, Larry & Curly" + */ + function unescape(string) { + return string == null ? '' : (string + '').replace(reEscapedHtml, unescapeHtmlChar); } /** @@ -3987,8 +3914,8 @@ * // => 'moe is 40' */ function chain(value) { - value = new LoDash(value); - value._chain = true; + value = new lodash(value); + value.__chain__ = true; return value; } @@ -4005,7 +3932,7 @@ * @returns {Mixed} Returns `value`. * @example * - * _.chain([1,2,3,200]) + * _.chain([1, 2, 3, 200]) * .filter(function(num) { return num % 2 == 0; }) * .tap(alert) * .map(function(num) { return num * num }) @@ -4032,7 +3959,7 @@ * // => [1, 2, 3] */ function wrapperChain() { - this._chain = true; + this.__chain__ = true; return this; } @@ -4049,7 +3976,7 @@ * // => [1, 2, 3] */ function wrapperValue() { - return this._wrapped; + return this.__wrapped__; } /*--------------------------------------------------------------------------*/ @@ -4061,7 +3988,7 @@ * @memberOf _ * @type String */ - lodash.VERSION = '0.5.2'; + lodash.VERSION = '0.7.0'; // assign static methods lodash.after = after; @@ -4078,7 +4005,6 @@ lodash.defer = defer; lodash.delay = delay; lodash.difference = difference; - lodash.drop = drop; lodash.escape = escape; lodash.every = every; lodash.extend = extend; @@ -4096,6 +4022,7 @@ lodash.indexOf = indexOf; lodash.initial = initial; lodash.intersection = intersection; + lodash.invert = invert; lodash.invoke = invoke; lodash.isArguments = isArguments; lodash.isArray = isArray; @@ -4110,12 +4037,14 @@ lodash.isNull = isNull; lodash.isNumber = isNumber; lodash.isObject = isObject; + lodash.isPlainObject = isPlainObject; lodash.isRegExp = isRegExp; lodash.isString = isString; lodash.isUndefined = isUndefined; lodash.keys = keys; lodash.last = last; lodash.lastIndexOf = lastIndexOf; + lodash.lateBind = lateBind; lodash.map = map; lodash.max = max; lodash.memoize = memoize; @@ -4123,10 +4052,14 @@ lodash.min = min; lodash.mixin = mixin; lodash.noConflict = noConflict; + lodash.object = object; + lodash.omit = omit; lodash.once = once; + lodash.pairs = pairs; lodash.partial = partial; lodash.pick = pick; lodash.pluck = pluck; + lodash.random = random; lodash.range = range; lodash.reduce = reduce; lodash.reduceRight = reduceRight; @@ -4143,6 +4076,7 @@ lodash.throttle = throttle; lodash.times = times; lodash.toArray = toArray; + lodash.unescape = unescape; lodash.union = union; lodash.uniq = uniq; lodash.uniqueId = uniqueId; @@ -4151,13 +4085,13 @@ lodash.without = without; lodash.wrap = wrap; lodash.zip = zip; - lodash.zipObject = zipObject; // assign aliases lodash.all = every; lodash.any = some; lodash.collect = map; lodash.detect = find; + lodash.drop = rest; lodash.each = forEach; lodash.foldl = reduce; lodash.foldr = reduceRight; @@ -4176,37 +4110,30 @@ /*--------------------------------------------------------------------------*/ - // assign private `LoDash` constructor's prototype - LoDash.prototype = lodash.prototype; - - // add all static functions to `LoDash.prototype` + // add all static functions to `lodash.prototype` mixin(lodash); - // add `LoDash.prototype.chain` after calling `mixin()` to avoid overwriting + // add `lodash.prototype.chain` after calling `mixin()` to avoid overwriting // it with the wrapped `lodash.chain` - LoDash.prototype.chain = wrapperChain; - LoDash.prototype.value = wrapperValue; + lodash.prototype.chain = wrapperChain; + lodash.prototype.value = wrapperValue; // add all mutator Array functions to the wrapper. forEach(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) { var func = ArrayProto[methodName]; - LoDash.prototype[methodName] = function() { - var value = this._wrapped; + lodash.prototype[methodName] = function() { + var value = this.__wrapped__; func.apply(value, arguments); - // Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array - // `shift()` and `splice()` functions that fail to remove the last element, - // `value[0]`, of array-like objects even though the `length` property is - // set to `0`. The `shift()` method is buggy in IE 8 compatibility mode, - // while `splice()` is buggy regardless of mode in IE < 9 and buggy in - // compatibility mode in IE 9. - if (value.length === 0) { + // avoid array-like object bugs with `Array#shift` and `Array#splice` in + // Firefox < 10 and IE < 9 + if (hasObjectSpliceBug && value.length === 0) { delete value[0]; } - if (this._chain) { - value = new LoDash(value); - value._chain = true; + if (this.__chain__) { + value = new lodash(value); + value.__chain__ = true; } return value; }; @@ -4216,13 +4143,13 @@ forEach(['concat', 'join', 'slice'], function(methodName) { var func = ArrayProto[methodName]; - LoDash.prototype[methodName] = function() { - var value = this._wrapped, + lodash.prototype[methodName] = function() { + var value = this.__wrapped__, result = func.apply(value, arguments); - if (this._chain) { - result = new LoDash(result); - result._chain = true; + if (this.__chain__) { + result = new lodash(result); + result.__chain__ = true; } return result; }; diff --git a/module/web/static/js/views/packageView.js b/module/web/static/js/views/packageView.js index 1fbcd0613..3b743b448 100644 --- a/module/web/static/js/views/packageView.js +++ b/module/web/static/js/views/packageView.js @@ -1,10 +1,12 @@ -define(['jquery', 'views/abstract/itemView', 'underscore', 'views/fileView', 'utils/lazyRequire'], +define(['jquery', 'views/abstract/itemView', 'underscore', 'views/fileView', 'utils/lazyRequire', 'flotpie'], function($, itemView, _, fileView, lazyLoader) { // Renders a single package item return itemView.extend({ tagName: 'li', + className: 'package-view', + template: _.template($("#template-package").html()), events: { 'click .load': 'load', 'click .delete': 'delete', @@ -22,14 +24,36 @@ define(['jquery', 'views/abstract/itemView', 'underscore', 'views/fileView', 'ut }, onDestroy: function() { - this.modal.off('filter:added', this.hide); // TODO + this.model.off('filter:added', this.hide); // TODO }, render: function() { - this.$el.html('Package ' + this.model.get('pid') + ': ' + this.model.get('name')); - this.$el.append($('<a class="load" href="#"> Load</a>')); - this.$el.append($('<a class="delete" href="#"> Delete</a>')); - this.$el.append($('<a class="show-dialog" href="#"> Show</a>')); + this.$el.html(this.template(this.model.toJSON())); + + var data = [ + { label: "Series1", data: 30}, + { label: "Series2", data: 90} + ]; + var pie = this.$('.package-graph'); + $.plot(pie, data, + { + series: { + pie: { + radius: 1, + show: true, + label: { + show: false + }, + offset: { + top: 0, + left: 0 + } + } + }, + legend: { + show: false + } + }); if (this.model.isLoaded()) { var ul = $('<ul></ul>'); diff --git a/module/web/templates/default/dashboard.html b/module/web/templates/default/dashboard.html index f6c6513aa..6a5d18fdd 100644 --- a/module/web/templates/default/dashboard.html +++ b/module/web/templates/default/dashboard.html @@ -1,20 +1,33 @@ {% extends 'default/base.html' %}
{% block title %}
- {{_("Dashboard")}} - {{ super()}}
+ {{ _("Dashboard") }} - {{ super() }}
{% endblock %}
{% block require %}
App.initPackageTree();
{% endblock %}
+{% block head %}
+ <script type="text/template" id="template-package">
+ <div>
+ Package <%= pid %>: <%= name %>
+ <div class="package-graph"></div>
+
+ <a class="load"> Load</a>
+ <a class="delete"> Delete</a>
+ <a class="show-dialog"> Show</a>
+ </div>
+ </script>
+{% endblock %}
+
{% block content %}
<ul id="dash-nav" class="nav nav-pills">
<li>
- <ul class="breadcrumb">
- <li><a href="#">{{ _("Home") }}</a> <span class="divider">/</span></li>
- <li><a href="#">Library</a> <span class="divider">/</span></li>
- <li class="active">Data</li>
- </ul>
+ <ul class="breadcrumb">
+ <li><a href="#">{{ _("Home") }}</a> <span class="divider">/</span></li>
+ <li><a href="#">Library</a> <span class="divider">/</span></li>
+ <li class="active">Data</li>
+ </ul>
</li>
<li style="float: right;">
|