summaryrefslogtreecommitdiffstats
path: root/module
diff options
context:
space:
mode:
Diffstat (limited to 'module')
-rw-r--r--module/database/DatabaseBackend.py1
-rw-r--r--module/database/FileDatabase.py5
-rw-r--r--module/datatypes/PyPackage.py9
-rw-r--r--module/remote/pyload.thrift21
-rw-r--r--module/remote/ttypes.py11
-rw-r--r--module/web/static/css/default/style.less65
-rw-r--r--module/web/static/js/default.js15
-rw-r--r--module/web/static/js/libs/jquery.flot-1.1.js2599
-rw-r--r--module/web/static/js/libs/jquery.flot.min.js6
-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.js36
-rw-r--r--module/web/templates/default/dashboard.html25
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 = {
- '&': '&amp;',
- '<': '&lt;',
- '"': '&quot;',
- "'": '&#x27;'
- };
-
/** 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 = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#x27;'
+ };
+
+ /** 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>&lt;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 `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#x27;`
+ * 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 &amp; 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;">