diff options
author | Costa Tsaousis (ktsaou) <costa@tsaousis.gr> | 2018-02-04 04:15:04 +0200 |
---|---|---|
committer | Costa Tsaousis (ktsaou) <costa@tsaousis.gr> | 2018-02-04 04:15:04 +0200 |
commit | 8b7cd623aa59ae5f2fc9057cf8986fcef1d8a55f (patch) | |
tree | 04d89f43fa5337b0bf5aff55f29e0e66636e5887 /web | |
parent | e1dff0634539ce63ab6d563263c1bcd45c12a96e (diff) |
even more d3pie optimizations
Diffstat (limited to 'web')
-rw-r--r-- | web/dashboard.js | 2 | ||||
-rw-r--r-- | web/lib/d3pie-0.2.1-netdata-2.js | 3739 |
2 files changed, 1871 insertions, 1870 deletions
diff --git a/web/dashboard.js b/web/dashboard.js index 5329d1ae32..112a4054c4 100644 --- a/web/dashboard.js +++ b/web/dashboard.js @@ -243,7 +243,7 @@ var NETDATA = window.NETDATA || {}; NETDATA.raphael_js = NETDATA.serverStatic + 'lib/raphael-2.2.4-min.js'; NETDATA.c3_js = NETDATA.serverStatic + 'lib/c3-0.4.18.min.js'; NETDATA.c3_css = NETDATA.serverStatic + 'css/c3-0.4.18.min.css'; - NETDATA.d3pie_js = NETDATA.serverStatic + 'lib/d3pie-0.2.1-netdata-2.js?v7'; + NETDATA.d3pie_js = NETDATA.serverStatic + 'lib/d3pie-0.2.1-netdata-2.js?v26'; NETDATA.d3_js = NETDATA.serverStatic + 'lib/d3-4.12.2.min.js'; NETDATA.morris_js = NETDATA.serverStatic + 'lib/morris-0.5.1.min.js'; NETDATA.morris_css = NETDATA.serverStatic + 'css/morris-0.5.1.css'; diff --git a/web/lib/d3pie-0.2.1-netdata-2.js b/web/lib/d3pie-0.2.1-netdata-2.js index 084756c7e6..e6ef064af6 100644 --- a/web/lib/d3pie-0.2.1-netdata-2.js +++ b/web/lib/d3pie-0.2.1-netdata-2.js @@ -21,1207 +21,1206 @@ } }(this, function() { - var _scriptName = "d3pie"; - var _version = "0.2.1"; + var _scriptName = "d3pie"; + var _version = "0.2.1"; - // used to uniquely generate IDs and classes, ensuring no conflict between multiple pies on the same page - var _uniqueIDCounter = 0; + // used to uniquely generate IDs and classes, ensuring no conflict between multiple pies on the same page + var _uniqueIDCounter = 0; - // this section includes all helper libs on the d3pie object. They're populated via grunt-template. Note: to keep - // the syntax highlighting from getting all messed up, I commented out each line. That REQUIRES each of the files - // to have an empty first line. Crumby, yes, but acceptable. - //// --------- _default-settings.js -----------/** + // this section includes all helper libs on the d3pie object. They're populated via grunt-template. Note: to keep + // the syntax highlighting from getting all messed up, I commented out each line. That REQUIRES each of the files + // to have an empty first line. Crumby, yes, but acceptable. + //// --------- _default-settings.js -----------/** /** * Contains the out-the-box settings for the script. Any of these settings that aren't explicitly overridden for the * d3pie instance will inherit from these. This is also included on the main website for use in the generation script. */ var defaultSettings = { - header: { - title: { - text: "", - color: "#333333", - fontSize: 18, - fontWeight: "bold", - font: "arial" - }, - subtitle: { - text: "", - color: "#666666", - fontSize: 14, - fontWeight: "bold", - font: "arial" - }, - location: "top-center", - titleSubtitlePadding: 8 - }, - footer: { - text: "", - color: "#666666", - fontSize: 14, - fontWeight: "bold", - font: "arial", - location: "left" - }, - size: { - canvasHeight: 500, - canvasWidth: 500, - pieInnerRadius: "0%", - pieOuterRadius: null - }, - data: { - sortOrder: "none", - ignoreSmallSegments: { - enabled: false, - valueType: "percentage", - value: null - }, - smallSegmentGrouping: { - enabled: false, - value: 1, - valueType: "percentage", - label: "Other", - color: "#cccccc" - }, - content: [] - }, - labels: { - outer: { - format: "label", - hideWhenLessThanPercentage: null, - pieDistance: 30 - }, - inner: { - format: "percentage", - hideWhenLessThanPercentage: null - }, - mainLabel: { - color: "#333333", - font: "arial", - fontWeight: "normal", - fontSize: 10 - }, - percentage: { - color: "#dddddd", - font: "arial", - fontWeight: "bold", - fontSize: 10, - decimalPlaces: 0 - }, - value: { - color: "#cccc44", - fontWeight: "bold", - font: "arial", - fontSize: 10 - }, - lines: { - enabled: true, - style: "curved", - color: "segment" - }, - truncation: { - enabled: false, - truncateLength: 30 - }, + header: { + title: { + text: "", + color: "#333333", + fontSize: 18, + fontWeight: "bold", + font: "arial" + }, + subtitle: { + text: "", + color: "#666666", + fontSize: 14, + fontWeight: "bold", + font: "arial" + }, + location: "top-center", + titleSubtitlePadding: 8 + }, + footer: { + text: "", + color: "#666666", + fontSize: 14, + fontWeight: "bold", + font: "arial", + location: "left" + }, + size: { + canvasHeight: 500, + canvasWidth: 500, + pieInnerRadius: "0%", + pieOuterRadius: null + }, + data: { + sortOrder: "none", + ignoreSmallSegments: { + enabled: false, + valueType: "percentage", + value: null + }, + smallSegmentGrouping: { + enabled: false, + value: 1, + valueType: "percentage", + label: "Other", + color: "#cccccc" + }, + content: [] + }, + labels: { + outer: { + format: "label", + hideWhenLessThanPercentage: null, + pieDistance: 30 + }, + inner: { + format: "percentage", + hideWhenLessThanPercentage: null + }, + mainLabel: { + color: "#333333", + font: "arial", + fontWeight: "normal", + fontSize: 10 + }, + percentage: { + color: "#dddddd", + font: "arial", + fontWeight: "bold", + fontSize: 10, + decimalPlaces: 0 + }, + value: { + color: "#cccc44", + fontWeight: "bold", + font: "arial", + fontSize: 10 + }, + lines: { + enabled: true, + style: "curved", + color: "segment" + }, + truncation: { + enabled: false, + truncateLength: 30 + }, formatter: null - }, - effects: { - load: { - effect: "none", // "default", commented in the code - speed: 1000 - }, - pullOutSegmentOnClick: { - effect: "none", // "bounce", commented in the code - speed: 300, - size: 10 - }, - highlightSegmentOnMouseover: false, - highlightLuminosity: -0.2 - }, - tooltips: { - enabled: false, - type: "placeholder", // caption|placeholder - string: "", - placeholderParser: null, - styles: { - fadeInSpeed: 250, - backgroundColor: "#000000", - backgroundOpacity: 0.5, - color: "#efefef", - borderRadius: 2, - font: "arial", - fontWeight: "bold", - fontSize: 10, - padding: 4 - } - }, - misc: { - colors: { - background: null, - segments: [ - "#2484c1", "#65a620", "#7b6888", "#a05d56", "#961a1a", "#d8d23a", "#e98125", "#d0743c", "#635222", "#6ada6a", - "#0c6197", "#7d9058", "#207f33", "#44b9b0", "#bca44a", "#e4a14b", "#a3acb2", "#8cc3e9", "#69a6f9", "#5b388f", - "#546e91", "#8bde95", "#d2ab58", "#273c71", "#98bf6e", "#4daa4b", "#98abc5", "#cc1010", "#31383b", "#006391", - "#c2643f", "#b0a474", "#a5a39c", "#a9c2bc", "#22af8c", "#7fcecf", "#987ac6", "#3d3b87", "#b77b1c", "#c9c2b6", - "#807ece", "#8db27c", "#be66a2", "#9ed3c6", "#00644b", "#005064", "#77979f", "#77e079", "#9c73ab", "#1f79a7" - ], - segmentStroke: "#ffffff" - }, - gradient: { - enabled: false, - percentage: 95, - color: "#000000" - }, - canvasPadding: { - top: 5, - right: 5, - bottom: 5, - left: 5 - }, - pieCenterOffset: { - x: 0, - y: 0 - }, - cssPrefix: null - }, - callbacks: { - onload: null, - onMouseoverSegment: null, - onMouseoutSegment: null, - onClickSegment: null - } + }, + effects: { + load: { + effect: "none", // "default", commented in the code + speed: 1000 + }, + pullOutSegmentOnClick: { + effect: "none", // "bounce", commented in the code + speed: 300, + size: 10 + }, + highlightSegmentOnMouseover: false, + highlightLuminosity: -0.2 + }, + tooltips: { + enabled: false, + type: "placeholder", // caption|placeholder + string: "", + placeholderParser: null, + styles: { + fadeInSpeed: 250, + backgroundColor: "#000000", + backgroundOpacity: 0.5, + color: "#efefef", + borderRadius: 2, + font: "arial", + fontWeight: "bold", + fontSize: 10, + padding: 4 + } + }, + misc: { + colors: { + background: null, + segments: [ + "#2484c1", "#65a620", "#7b6888", "#a05d56", "#961a1a", "#d8d23a", "#e98125", "#d0743c", "#635222", "#6ada6a", + "#0c6197", "#7d9058", "#207f33", "#44b9b0", "#bca44a", "#e4a14b", "#a3acb2", "#8cc3e9", "#69a6f9", "#5b388f", + "#546e91", "#8bde95", "#d2ab58", "#273c71", "#98bf6e", "#4daa4b", "#98abc5", "#cc1010", "#31383b", "#006391", + "#c2643f", "#b0a474", "#a5a39c", "#a9c2bc", "#22af8c", "#7fcecf", "#987ac6", "#3d3b87", "#b77b1c", "#c9c2b6", + "#807ece", "#8db27c", "#be66a2", "#9ed3c6", "#00644b", "#005064", "#77979f", "#77e079", "#9c73ab", "#1f79a7" + ], + segmentStroke: "#ffffff" + }, + gradient: { + enabled: false, + percentage: 95, + color: "#000000" + }, + canvasPadding: { + top: 5, + right: 5, + bottom: 5, + left: 5 + }, + pieCenterOffset: { + x: 0, + y: 0 + }, + cssPrefix: null + }, + callbacks: { + onload: null, + onMouseoverSegment: null, + onMouseoutSegment: null, + onClickSegment: null + } }; - //// --------- validate.js ----------- + //// --------- validate.js ----------- var validate = { - // called whenever a new pie chart is created - initialCheck: function(pie) { - var cssPrefix = pie.cssPrefix; - var element = pie.element; - var options = pie.options; - - // confirm d3 is available [check minimum version] - if (!window.d3 || !window.d3.hasOwnProperty("version")) { - console.error("d3pie error: d3 is not available"); - return false; - } - - // confirm element is either a DOM element or a valid string for a DOM element - if (!(element instanceof HTMLElement || element instanceof SVGElement)) { - console.error("d3pie error: the first d3pie() param must be a valid DOM element (not jQuery) or a ID string."); - return false; - } - - // confirm the CSS prefix is valid. It has to start with a-Z and contain nothing but a-Z0-9_- - if (!(/[a-zA-Z][a-zA-Z0-9_-]*$/.test(cssPrefix))) { - console.error("d3pie error: invalid options.misc.cssPrefix"); - return false; - } - - // confirm some data has been supplied - if (!helpers.isArray(options.data.content)) { - console.error("d3pie error: invalid config structure: missing data.content property."); - return false; - } - if (options.data.content.length === 0) { - console.error("d3pie error: no data supplied."); - return false; - } - - // clear out any invalid data. Each data row needs a valid positive number and a label - var data = []; - for (var i=0; i<options.data.content.length; i++) { - if (typeof options.data.content[i].value !== "number" || isNaN(options.data.content[i].value)) { - console.log("not valid: ", options.data.content[i]); - continue; - } - if (options.data.content[i].value <= 0) { - console.log("not valid - should have positive value: ", options.data.content[i]); - continue; - } - data.push(options.data.content[i]); - } - pie.options.data.content = data; - - // labels.outer.hideWhenLessThanPercentage - 1-100 - // labels.inner.hideWhenLessThanPercentage - 1-100 - - return true; - } + // called whenever a new pie chart is created + initialCheck: function(pie) { + var cssPrefix = pie.cssPrefix; + var element = pie.element; + var options = pie.options; + + // confirm d3 is available [check minimum version] + if (!window.d3 || !window.d3.hasOwnProperty("version")) { + console.error("d3pie error: d3 is not available"); + return false; + } + + // confirm element is either a DOM element or a valid string for a DOM element + if (!(element instanceof HTMLElement || element instanceof SVGElement)) { + console.error("d3pie error: the first d3pie() param must be a valid DOM element (not jQuery) or a ID string."); + return false; + } + + // confirm the CSS prefix is valid. It has to start with a-Z and contain nothing but a-Z0-9_- + if (!(/[a-zA-Z][a-zA-Z0-9_-]*$/.test(cssPrefix))) { + console.error("d3pie error: invalid options.misc.cssPrefix"); + return false; + } + + // confirm some data has been supplied + if (!helpers.isArray(options.data.content)) { + console.error("d3pie error: invalid config structure: missing data.content property."); + return false; + } + if (options.data.content.length === 0) { + console.error("d3pie error: no data supplied."); + return false; + } + + // clear out any invalid data. Each data row needs a valid positive number and a label + var data = []; + for (var i=0; i<options.data.content.length; i++) { + if (typeof options.data.content[i].value !== "number" || isNaN(options.data.content[i].value)) { + console.log("not valid: ", options.data.content[i]); + continue; + } + if (options.data.content[i].value <= 0) { + console.log("not valid - should have positive value: ", options.data.content[i]); + continue; + } + data.push(options.data.content[i]); + } + pie.options.data.content = data; + + // labels.outer.hideWhenLessThanPercentage - 1-100 + // labels.inner.hideWhenLessThanPercentage - 1-100 + + return true; + } }; - //// --------- helpers.js ----------- + //// --------- helpers.js ----------- var helpers = { - // creates the SVG element - addSVGSpace: function(pie) { - var element = pie.element; - var canvasWidth = pie.options.size.canvasWidth; - var canvasHeight = pie.options.size.canvasHeight; - var backgroundColor = pie.options.misc.colors.background; - - var svg = d3.select(element).append("svg:svg") - .attr("width", canvasWidth) - .attr("height", canvasHeight); - - if (backgroundColor !== "transparent") { - svg.style("background-color", function() { return backgroundColor; }); - } - - return svg; - }, - - shuffleArray: function(array) { - var currentIndex = array.length, tmpVal, randomIndex; - - while (0 !== currentIndex) { - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - - // and swap it with the current element - tmpVal = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = tmpVal; - } - return array; - }, - - processObj: function(obj, is, value) { - if (typeof is === 'string') { - return helpers.processObj(obj, is.split('.'), value); - } else if (is.length === 1 && value !== undefined) { + // creates the SVG element + addSVGSpace: function(pie) { + var element = pie.element; + var canvasWidth = pie.options.size.canvasWidth; + var canvasHeight = pie.options.size.canvasHeight; + var backgroundColor = pie.options.misc.colors.background; + + var svg = d3.select(element).append("svg:svg") + .attr("width", canvasWidth) + .attr("height", canvasHeight); + + if (backgroundColor !== "transparent") { + svg.style("background-color", function() { return backgroundColor; }); + } + + return svg; + }, + + shuffleArray: function(array) { + var currentIndex = array.length, tmpVal, randomIndex; + + while (0 !== currentIndex) { + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + + // and swap it with the current element + tmpVal = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = tmpVal; + } + return array; + }, + + processObj: function(obj, is, value) { + if (typeof is === 'string') { + return helpers.processObj(obj, is.split('.'), value); + } else if (is.length === 1 && value !== undefined) { obj[is[0]] = value; - return obj[is[0]]; - } else if (is.length === 0) { - return obj; - } else { - return helpers.processObj(obj[is[0]], is.slice(1), value); - } - }, - - getDimensions: function(el) { - if(typeof el === 'string') - el = document.getElementById(el); - - var w = 0, h = 0; - if (el) { - var dimensions = el.getBBox(); - w = dimensions.width; - h = dimensions.height; - } - else { - console.log("error: getDimensions() " + id + " not found."); - } - - return { w: w, h: h }; - }, - - /** - * This is based on the SVG coordinate system, where top-left is 0,0 and bottom right is n-n. - * @param r1 - * @param r2 - * @returns {boolean} - */ - rectIntersect: function(r1, r2) { - var returnVal = ( - // r2.left > r1.right - (r2.x > (r1.x + r1.w)) || - - // r2.right < r1.left - ((r2.x + r2.w) < r1.x) || - - // r2.top < r1.bottom - ((r2.y + r2.h) < r1.y) || - - // r2.bottom > r1.top - (r2.y > (r1.y + r1.h)) - ); - - return !returnVal; - }, - - /** - * Returns a lighter/darker shade of a hex value, based on a luminance value passed. - * @param hex a hex color value such as “#abc” or “#123456″ (the hash is optional) - * @param lum the luminosity factor: -0.1 is 10% darker, 0.2 is 20% lighter, etc. - * @returns {string} - */ - getColorShade: function(hex, lum) { - - // validate hex string - hex = String(hex).replace(/[^0-9a-f]/gi, ''); - if (hex.length < 6) { - hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; - } - lum = lum || 0; - - // convert to decimal and change luminosity - var newHex = "#"; - for (var i=0; i<3; i++) { - var c = parseInt(hex.substr(i * 2, 2), 16); - c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16); - newHex += ("00" + c).substr(c.length); - } - - return newHex; - }, - - /** - * Users can choose to specify segment colors in three ways (in order of precedence): - * 1. include a "color" attribute for each row in data.content - * 2. include a misc.colors.segments property which contains an array of hex codes - * 3. specify nothing at all and rely on this lib provide some reasonable defaults - * - * This function sees what's included and populates this.options.colors with whatever's required - * for this pie chart. - * @param data - */ - initSegmentColors: function(pie) { - var data = pie.options.data.content; - var colors = pie.options.misc.colors.segments; - - // TODO this needs a ton of error handling - - var finalColors = []; - for (var i=0; i<data.length; i++) { - if (data[i].hasOwnProperty("color")) { - finalColors.push(data[i].color); - } else { - finalColors.push(colors[i]); - } - } - - return finalColors; - }, - - applySmallSegmentGrouping: function(data, smallSegmentGrouping) { - var totalSize; - if (smallSegmentGrouping.valueType === "percentage") { - totalSize = math.getTotalPieSize(data); - } - - // loop through each data item - var newData = []; - var groupedData = []; - var totalGroupedData = 0; - for (var i=0; i<data.length; i++) { - if (smallSegmentGrouping.valueType === "percentage") { - var dataPercent = (data[i].value / totalSize) * 100; - if (dataPercent <= smallSegmentGrouping.value) { - groupedData.push(data[i]); - totalGroupedData += data[i].value; - continue; - } - data[i].isGrouped = false; - newData.push(data[i]); - } else { - if (data[i].value <= smallSegmentGrouping.value) { - groupedData.push(data[i]); - totalGroupedData += data[i].value; - continue; - } - data[i].isGrouped = false; - newData.push(data[i]); - } - } - - // we're done! See if there's any small segment groups to add - if (groupedData.length) { - newData.push({ - color: smallSegmentGrouping.color, - label: smallSegmentGrouping.label, - value: totalGroupedData, - isGrouped: true, - groupedData: groupedData - }); - } - - return newData; - }, - - // for debugging - showPoint: function(svg, x, y) { - svg.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).style("fill", "black"); - }, - - isFunction: function(functionToCheck) { - var getType = {}; - return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; - }, - - isArray: function(o) { - return Object.prototype.toString.call(o) === '[object Array]'; - } + return obj[is[0]]; + } else if (is.length === 0) { + return obj; + } else { + return helpers.processObj(obj[is[0]], is.slice(1), value); + } + }, + + getDimensions: function(el) { + if(typeof el === 'string') + el = document.getElementById(el); + + var w = 0, h = 0; + if (el) { + var dimensions = el.getBBox(); + w = dimensions.width; + h = dimensions.height; + } + else { + console.log("error: getDimensions() " + id + " not found."); + } + + return { w: w, h: h }; + }, + + /** + * This is based on the SVG coordinate system, where top-left is 0,0 and bottom right is n-n. + * @param r1 + * @param r2 + * @returns {boolean} + */ + rectIntersect: function(r1, r2) { + var returnVal = ( + // r2.left > r1.right + (r2.x > (r1.x + r1.w)) || + + // r2.right < r1.left + ((r2.x + r2.w) < r1.x) || + + // r2.top < r1.bottom + ((r2.y + r2.h) < r1.y) || + + // r2.bottom > r1.top + (r2.y > (r1.y + r1.h)) + ); + + return !returnVal; + }, + + /** + * Returns a lighter/darker shade of a hex value, based on a luminance value passed. + * @param hex a hex color value such as “#abc” or “#123456″ (the hash is optional) + * @param lum the luminosity factor: -0.1 is 10% darker, 0.2 is 20% lighter, etc. + * @returns {string} + */ + getColorShade: function(hex, lum) { + + // validate hex string + hex = String(hex).replace(/[^0-9a-f]/gi, ''); + if (hex.length < 6) { + hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; + } + lum = lum || 0; + + // convert to decimal and change luminosity + var newHex = "#"; + for (var i=0; i<3; i++) { + var c = parseInt(hex.substr(i * 2, 2), 16); + c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16); + newHex += ("00" + c).substr(c.length); + } + + return newHex; + }, + + /** + * Users can choose to specify segment colors in three ways (in order of precedence): + * 1. include a "color" attribute for each row in data.content + * 2. include a misc.colors.segments property which contains an array of hex codes + * 3. specify nothing at all and rely on this lib provide some reasonable defaults + * + * This function sees what's included and populates this.options.colors with whatever's required + * for this pie chart. + * @param data + */ + initSegmentColors: function(pie) { + var data = pie.options.data.content; + var colors = pie.options.misc.colors.segments; + + // TODO this needs a ton of error handling + + var finalColors = []; + for (var i=0; i<data.length; i++) { + if (data[i].hasOwnProperty("color")) { + finalColors.push(data[i].color); + } else { + finalColors.push(colors[i]); + } + } + + return finalColors; + }, + + applySmallSegmentGrouping: function(data, smallSegmentGrouping) { + var totalSize; + if (smallSegmentGrouping.valueType === "percentage") { + totalSize = math.getTotalPieSize(data); + } + + // loop through each data item + var newData = []; + var groupedData = []; + var totalGroupedData = 0; + for (var i=0; i<data.length; i++) { + if (smallSegmentGrouping.valueType === "percentage") { + var dataPercent = (data[i].value / totalSize) * 100; + if (dataPercent <= smallSegmentGrouping.value) { + groupedData.push(data[i]); + totalGroupedData += data[i].value; + continue; + } + data[i].isGrouped = false; + newData.push(data[i]); + } else { + if (data[i].value <= smallSegmentGrouping.value) { + groupedData.push(data[i]); + totalGroupedData += data[i].value; + continue; + } + data[i].isGrouped = false; + newData.push(data[i]); + } + } + + // we're done! See if there's any small segment groups to add + if (groupedData.length) { + newData.push({ + color: smallSegmentGrouping.color, + label: smallSegmentGrouping.label, + value: totalGroupedData, + isGrouped: true, + groupedData: groupedData + }); + } + + return newData; + }, + + // for debugging + showPoint: function(svg, x, y) { + svg.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).style("fill", "black"); + }, + + isFunction: function(functionToCheck) { + var getType = {}; + return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; + }, + + isArray: function(o) { + return Object.prototype.toString.call(o) === '[object Array]'; + } }; // taken from jQuery var extend = function() { - var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false, - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - class2type = { - "[object Boolean]": "boolean", - "[object Number]": "number", - "[object String]": "string", - "[object Function]": "function", - "[object Array]": "array", - "[object Date]": "date", - "[object RegExp]": "regexp", - "[object Object]": "object" - }, - - jQuery = { - isFunction: function (obj) { - return jQuery.type(obj) === "function"; - }, - isArray: Array.isArray || - function (obj) { - return jQuery.type(obj) === "array"; - }, - isWindow: function (obj) { - return obj !== null && obj === obj.window; - }, - isNumeric: function (obj) { - return !isNaN(parseFloat(obj)) && isFinite(obj); - }, - type: function (obj) { - return obj === null ? String(obj) : class2type[toString.call(obj)] || "object"; - }, - isPlainObject: function (obj) { - if (!obj || jQuery.type(obj) !== "object" || obj.nodeType) { - return false; - } - try { - if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { - return false; - } - } catch (e) { - return false; - } - var key; - for (key in obj) {} - return key === undefined || hasOwn.call(obj, key); - } - }; - if (typeof target === "boolean") { - deep = target; - target = arguments[1] || {}; - i = 2; - } - if (typeof target !== "object" && !jQuery.isFunction(target)) { - target = {}; - } - if (length === i) { - target = this; - --i; - } - for (i; i < length; i++) { - if ((options = arguments[i]) !== null) { - for (name in options) { - src = target[name]; - copy = options[name]; - if (target === copy) { - continue; - } - if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) { - if (copyIsArray) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - // WARNING: RECURSION - target[name] = extend(deep, clone, copy); - } else if (copy !== undefined) { - target[name] = copy; - } - } - } - } - return target; + var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false, + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + class2type = { + "[object Boolean]": "boolean", + "[object Number]": "number", + "[object String]": "string", + "[object Function]": "function", + "[object Array]": "array", + "[object Date]": "date", + "[object RegExp]": "regexp", + "[object Object]": "object" + }, + + jQuery = { + isFunction: function (obj) { + return jQuery.type(obj) === "function"; + }, + isArray: Array.isArray || + function (obj) { + return jQuery.type(obj) === "array"; + }, + isWindow: function (obj) { + return obj !== null && obj === obj.window; + }, + isNumeric: function (obj) { + return !isNaN(parseFloat(obj)) && isFinite(obj); + }, + type: function (obj) { + return obj === null ? String(obj) : class2type[toString.call(obj)] || "object"; + }, + isPlainObject: function (obj) { + if (!obj || jQuery.type(obj) !== "object" || obj.nodeType) { + return false; + } + try { + if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { + return false; + } + } catch (e) { + return false; + } + var key; + for (key in obj) {} + return key === undefined || hasOwn.call(obj, key); + } + }; + if (typeof target === "boolean") { + deep = target; + target = arguments[1] || {}; + i = 2; + } + if (typeof target !== "object" && !jQuery.isFunction(target)) { + target = {}; + } + if (length === i) { + target = this; + --i; + } + for (i; i < length; i++) { + if ((options = arguments[i]) !== null) { + for (name in options) { + src = target[name]; + copy = options[name]; + if (target === copy) { + continue; + } + if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) { + if (copyIsArray) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + // WARNING: RECURSION + target[name] = extend(deep, clone, copy); + } else if ( |