From 87082533b5dd4df23fb60646c30122aa4bbf2e36 Mon Sep 17 00:00:00 2001 From: John Kiernander Date: Wed, 22 Jan 2014 00:01:41 +0000 Subject: [PATCH] Added tests to lint and cleaned existing --- Gruntfile.js | 5 +- dist/dimple.v1.1.4.js | 4263 +++++++++++++++++ dist/dimple.v1.1.4.min.js | 2 + examples/advanced_bar_labels.html | 2 +- examples/advanced_bullet.html | 2 +- examples/advanced_dynamic_line_color.html | 2 +- examples/advanced_grouped_mekko.html | 2 +- examples/advanced_interactive_legends.html | 2 +- examples/advanced_lollipop_with_hover.html | 2 +- examples/advanced_matrix.html | 2 +- examples/advanced_pong.html | 2 +- examples/advanced_price_range_lollipop.html | 2 +- examples/advanced_responsive_sizing.html | 2 +- examples/advanced_storyboard_control.html | 2 +- examples/advanced_time_axis.html | 2 +- examples/advanced_trellis_bar.html | 2 +- examples/advanced_waterfall.html | 2 +- examples/areas_horizontal.html | 2 +- examples/areas_horizontal_grouped.html | 2 +- examples/areas_horizontal_grouped_100pct.html | 2 +- .../areas_horizontal_grouped_stacked.html | 2 +- examples/areas_horizontal_stacked.html | 2 +- examples/areas_horizontal_stacked_100pct.html | 2 +- examples/areas_vertical.html | 2 +- examples/areas_vertical_grouped.html | 2 +- examples/areas_vertical_grouped_100pct.html | 2 +- examples/areas_vertical_grouped_stacked.html | 2 +- examples/areas_vertical_stacked.html | 2 +- examples/areas_vertical_stacked_100pct.html | 2 +- examples/bars_horizontal.html | 2 +- examples/bars_horizontal_grouped.html | 2 +- examples/bars_horizontal_grouped_stacked.html | 2 +- ...ars_horizontal_grouped_stacked_100pct.html | 2 +- examples/bars_horizontal_mekko.html | 2 +- examples/bars_horizontal_stacked.html | 2 +- examples/bars_horizontal_stacked_100pct.html | 2 +- examples/bars_matrix.html | 2 +- examples/bars_vertical.html | 2 +- examples/bars_vertical_grouped.html | 2 +- examples/bars_vertical_grouped_stacked.html | 2 +- .../bars_vertical_grouped_stacked_100pct.html | 2 +- examples/bars_vertical_mekko.html | 2 +- examples/bars_vertical_stacked.html | 2 +- examples/bars_vertical_stacked_100pct.html | 2 +- examples/bubbles_horizontal_grouped.html | 2 +- examples/bubbles_horizontal_lollipop.html | 2 +- examples/bubbles_matrix.html | 2 +- examples/bubbles_standard.html | 2 +- examples/bubbles_vertical_grouped.html | 2 +- examples/bubbles_vertical_lollipop.html | 2 +- examples/lines_horizontal.html | 2 +- examples/lines_horizontal_grouped.html | 2 +- .../lines_horizontal_grouped_stacked.html | 2 +- examples/lines_horizontal_stacked.html | 2 +- examples/lines_vertical.html | 2 +- examples/lines_vertical_grouped.html | 2 +- examples/lines_vertical_grouped_stacked.html | 2 +- examples/lines_vertical_stacked.html | 2 +- examples/scatter_horizontal_grouped.html | 2 +- examples/scatter_horizontal_lollipop.html | 2 +- examples/scatter_matrix.html | 2 +- examples/scatter_standard.html | 2 +- examples/scatter_vertical_grouped.html | 2 +- examples/scatter_vertical_lollipop.html | 2 +- lib/qunit/qunit-1.11.0.css | 244 - lib/qunit/qunit-1.11.0.js | 2152 --------- package.json | 2 +- src/begin.js | 2 +- test/methods/_getOrderList.spec.js | 182 +- test/methods/_rollUp.spec.js | 201 +- test/methods/newSvg.spec.js | 109 +- 71 files changed, 4574 insertions(+), 2710 deletions(-) create mode 100644 dist/dimple.v1.1.4.js create mode 100644 dist/dimple.v1.1.4.min.js delete mode 100644 lib/qunit/qunit-1.11.0.css delete mode 100644 lib/qunit/qunit-1.11.0.js diff --git a/Gruntfile.js b/Gruntfile.js index 20d3c0a..ecd9a32 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -56,6 +56,7 @@ module.exports = function(grunt) { jslint: { files: [ 'Gruntfile.js', + 'test/**/*.spec.js', 'dist/<%= pkg.name %>.v<%= pkg.version %>.js' ], directives: { @@ -64,7 +65,9 @@ module.exports = function(grunt) { predef: [ 'd3', 'module', - 'console' + 'console', + 'jasmine', + 'dimple' ] } }, diff --git a/dist/dimple.v1.1.4.js b/dist/dimple.v1.1.4.js new file mode 100644 index 0000000..34b2524 --- /dev/null +++ b/dist/dimple.v1.1.4.js @@ -0,0 +1,4263 @@ +// Copyright: 2013 PMSI-AlignAlytics +// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" +// Source: /src/objects/begin.js + +// Create the stub object +var dimple = { + version: "1.1.4", + plot: {}, + aggregateMethod: {} +}; + +// Wrap all application code in a self-executing function +(function () { + "use strict"; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/begin.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis + dimple.axis = function (chart, position, categoryFields, measure, timeField) { + + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-chart + this.chart = chart; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-position + this.position = position; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-categoryFields + this.categoryFields = (timeField === null || timeField === undefined ? categoryFields : [].concat(timeField)); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-measure + this.measure = measure; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-timeField + this.timeField = timeField; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-floatingBarWidth + this.floatingBarWidth = 5; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-hidden + this.hidden = false; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-showPercent + this.showPercent = false; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-colors + this.colors = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-overrideMin + this.overrideMin = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-overrideMax + this.overrideMax = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-shapes + this.shapes = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-showGridlines + this.showGridlines = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-gridlineShapes + this.gridlineShapes = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-titleShape + this.titleShape = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-dateParseFormat + this.dateParseFormat = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-tickFormat + this.tickFormat = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-timePeriod + this.timePeriod = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-timeInterval + this.timeInterval = 1; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-useLog + this.useLog = false; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-logBase + this.logBase = 10; + + // The scale determined by the update method + this._scale = null; + // The minimum and maximum axis values + this._min = 0; + this._max = 0; + // Chart origin before and after an update. This helps + // with transitions + this._previousOrigin = null; + this._origin = null; + // The order definition array + this._orderRules = []; + // The group order definition array + this._groupOrderRules = []; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/_draw.js + this._draw = null; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/_getFormat.js + this._getFormat = function () { + var returnFormat, + max, + min, + len, + chunks, + suffix, + dp; + if (this.tickFormat !== null && this.tickFormat !== undefined) { + if (this._hasTimeField()) { + returnFormat = d3.time.format(this.tickFormat); + } else { + returnFormat = d3.format(this.tickFormat); + } + } else if (this.showPercent) { + returnFormat = d3.format("%"); + } else if (this.useLog && this.measure !== null) { + // With linear axes the range is used to apply uniform + // formatting but with a log axis it is based on each number + // independently + returnFormat = function (n) { + var l = Math.floor(Math.abs(n), 0).toString().length, + c = Math.min(Math.floor((l - 1) / 3), 4), + s = "kmBT".substring(c - 1, c), + d = (Math.round((n / Math.pow(1000, c)) * 10).toString().slice(-1) === "0" ? 0 : 1); + return (n === 0 ? 0 : d3.format(",." + d + "f")(n / Math.pow(1000, c)) + s); + }; + } else if (this.measure !== null) { + max = Math.floor(Math.abs(this._max), 0).toString(); + min = Math.floor(Math.abs(this._min), 0).toString(); + len = Math.max(min.length, max.length); + if (len > 3) { + chunks = Math.min(Math.floor((len - 1) / 3), 4); + suffix = "kmBT".substring(chunks - 1, chunks); + dp = (len - chunks * 3 <= 1 ? 1 : 0); + returnFormat = function (n) { + return (n === 0 ? 0 : d3.format(",." + dp + "f")(n / Math.pow(1000, chunks)) + suffix); + }; + } else { + dp = (len <= 1 ? 1 : 0); + returnFormat = d3.format(",." + dp + "f"); + } + } else { + returnFormat = function (n) { return n; }; + } + return returnFormat; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/_getTimePeriod.js + this._getTimePeriod = function () { + // A javascript date object + var outPeriod = this.timePeriod, + maxPeriods = 30, + diff = this._max - this._min; + if (this._hasTimeField && (this.timePeriod === null || this.timePeriod === undefined)) { + // Calculate using millisecond values for speed. Using the date range requires creating an array + // which in the case of seconds kills the browser. All constants are straight sums of milliseconds + // except months taken as (86400000 * 365.25) / 12 = 2629800000 + if (diff / 1000 <= maxPeriods) { + outPeriod = d3.time.seconds; + } else if (diff / 60000 <= maxPeriods) { + outPeriod = d3.time.minutes; + } else if (diff / 3600000 <= maxPeriods) { + outPeriod = d3.time.hours; + } else if (diff / 86400000 <= maxPeriods) { + outPeriod = d3.time.days; + } else if (diff / 604800000 <= maxPeriods) { + outPeriod = d3.time.weeks; + } else if (diff / 2629800000 <= maxPeriods) { + outPeriod = d3.time.months; + } else { + outPeriod = d3.time.years; + } + } + // Return the date + return outPeriod; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/_hasCategories.js + this._hasCategories = function () { + return (this.categoryFields !== null && this.categoryFields !== undefined && this.categoryFields.length > 0); + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/_hasMeasure.js + this._hasMeasure = function () { + return (this.measure !== null && this.measure !== undefined); + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/_hasTimeField.js + this._hasTimeField = function () { + return (this.timeField !== null && this.timeField !== undefined); + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/_parseDate.js + this._parseDate = function (inDate) { + // A javascript date object + var outDate; + if (this.dateParseFormat === null || this.dateParseFormat === undefined) { + // Moved this into the condition so that using epoch time requires no data format to be set. + // For example 20131122 might be parsed as %Y%m%d not treated as epoch time. + if (!isNaN(inDate)) { + // If inDate is a number, assume it's epoch time + outDate = new Date(inDate); + } else { + // If nothing has been explicity defined you are in the hands of the browser gods + // may they smile upon you... + outDate = Date.parse(inDate); + } + } else { + outDate = d3.time.format(this.dateParseFormat).parse(inDate); + } + // Return the date + return outDate; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/_update.js + this._update = function (refactor) { + + var distinctCats = [], + ticks, + step, + remainder, + origin, + getOrderedCategories = function (self, axPos, oppPos) { + var category = self.categoryFields[0], + sortBy = category, + desc = false, + isDate = true, + i, + definitions = []; + // Check whether this field is a date + for (i = 0; i < self.chart.data.length; i += 1) { + if (isNaN(self._parseDate(self.chart.data[i][category]))) { + isDate = false; + break; + } + } + if (!isDate) { + // Find the first series which connects this axis to another + self.chart.series.forEach(function (s) { + if (s[axPos] === self && s[oppPos]._hasMeasure()) { + sortBy = s[oppPos].measure; + desc = true; + } + }, this); + } + definitions = self._orderRules.concat({ ordering : sortBy, desc : desc }); + return dimple._getOrderedList(self.chart.data, category, definitions); + }; + + // If the axis is a percentage type axis the bounds must be between -1 and 1. Sometimes + // binary rounding means it can fall outside that bound so tidy up here + this._min = (this.showPercent && this._min < -1 ? -1 : this._min); + this._max = (this.showPercent && this._max > 1 ? 1 : this._max); + + // Override or round the min or max + this._min = (this.overrideMin !== null ? this.overrideMin : this._min); + this._max = (this.overrideMax !== null ? this.overrideMax : this._max); + + // If this is an x axis + if (this.position === "x") { + if (this._hasTimeField()) { + this._scale = d3.time.scale() + .rangeRound([this.chart._xPixels(), this.chart._xPixels() + this.chart._widthPixels()]) + .domain([this._min, this._max]); + } else if (this.useLog) { + this._scale = d3.scale.log() + .range([this.chart._xPixels(), this.chart._xPixels() + this.chart._widthPixels()]) + .domain([ + (this._min === 0 ? Math.pow(this.logBase, -1) : this._min), + (this._max === 0 ? -1 * Math.pow(this.logBase, -1) : this._max) + ]) + .clamp(true) + .base(this.logBase) + .nice(); + } else if (this.measure === null || this.measure === undefined) { + distinctCats = getOrderedCategories(this, "x", "y"); + this._scale = d3.scale.ordinal() + .rangePoints([this.chart._xPixels(), this.chart._xPixels() + this.chart._widthPixels()]) + .domain(distinctCats.concat([""])); + } else { + this._scale = d3.scale.linear() + .range([this.chart._xPixels(), this.chart._xPixels() + this.chart._widthPixels()]) + .domain([this._min, this._max]).nice(); + } + // If it's visible, orient it at the top or bottom if it's first or second respectively + if (!this.hidden) { + switch (this.chart._axisIndex(this, "x")) { + case 0: + this._draw = d3.svg.axis() + .orient("bottom") + .scale(this._scale); + break; + case 1: + this._draw = d3.svg.axis() + .orient("top") + .scale(this._scale); + break; + default: + break; + } + } + } else if (this.position === "y") { + if (this._hasTimeField()) { + this._scale = d3.time.scale() + .rangeRound([this.chart._yPixels() + this.chart._heightPixels(), this.chart._yPixels()]) + .domain([this._min, this._max]); + } else if (this.useLog) { + this._scale = d3.scale.log() + .range([this.chart._yPixels() + this.chart._heightPixels(), this.chart._yPixels()]) + .domain([ + (this._min === 0 ? Math.pow(this.logBase, -1) : this._min), + (this._max === 0 ? -1 * Math.pow(this.logBase, -1) : this._max) + ]) + .clamp(true) + .base(this.logBase) + .nice(); + } else if (this.measure === null || this.measure === undefined) { + distinctCats = getOrderedCategories(this, "y", "x"); + this._scale = d3.scale.ordinal() + .rangePoints([this.chart._yPixels() + this.chart._heightPixels(), this.chart._yPixels()]) + .domain(distinctCats.concat([""])); + } else { + this._scale = d3.scale.linear() + .range([this.chart._yPixels() + this.chart._heightPixels(), this.chart._yPixels()]) + .domain([this._min, this._max]) + .nice(); + } + // if it's visible, orient it at the left or right if it's first or second respectively + if (!this.hidden) { + switch (this.chart._axisIndex(this, "y")) { + case 0: + this._draw = d3.svg.axis() + .orient("left") + .scale(this._scale); + break; + case 1: + this._draw = d3.svg.axis() + .orient("right") + .scale(this._scale); + break; + default: + break; + } + } + } else if (this.position.length > 0 && this.position[0] === "z") { + if (this.useLog) { + this._scale = d3.scale.log() + .range([this.chart._heightPixels() / 300, this.chart._heightPixels() / 10]) + .domain([ + (this._min === 0 ? Math.pow(this.logBase, -1) : this._min), + (this._max === 0 ? -1 * Math.pow(this.logBase, -1) : this._max) + ]) + .clamp(true) + .base(this.logBase); + } else { + this._scale = d3.scale.linear() + .range([this.chart._heightPixels() / 300, this.chart._heightPixels() / 10]) + .domain([this._min, this._max]); + } + } else if (this.position.length > 0 && this.position[0] === "c") { + this._scale = d3.scale.linear() + .range([0, (this.colors === null || this.colors.length === 1 ? 1 : this.colors.length - 1)]) + .domain([this._min, this._max]); + } + // Check that the axis ends on a labelled tick + if ((refactor === null || refactor === undefined || refactor === false) && !this._hasTimeField() && this._scale !== null && this._scale.ticks !== null && this._scale.ticks !== undefined && this._scale.ticks(10).length > 0 && (this.position === "x" || this.position === "y")) { + + // Get the ticks determined based on a split of 10 + ticks = this._scale.ticks(10); + // Get the step between ticks + step = ticks[1] - ticks[0]; + // Get the remainder + remainder = ((this._max - this._min) % step).toFixed(0); + + // If the remainder is not zero + if (remainder !== 0) { + // Set the bounds + this._max = Math.ceil(this._max / step) * step; + this._min = Math.floor(this._min / step) * step; + // Recursively call the method to recalculate the scale. This shouldn't enter this block again. + this._update(true); + } + } + + // Populate the origin. Previously this incorrectly looked up 0 on the axis which only works + // for measure axes leading to Issue #19. This fix uses the first category value in cases where + // one is required. + if (distinctCats !== null && distinctCats !== undefined && distinctCats.length > 0) { + origin = this._scale.copy()(distinctCats[0]); + } else { + origin = this._scale.copy()(0); + } + + if (this._origin !== origin) { + this._previousOrigin = (this._origin === null ? origin : this._origin); + this._origin = origin; + } + + // Return axis for chaining + return this; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/addGroupOrderRule.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-addGroupOrderRule + this.addGroupOrderRule = function (ordering, desc) { + this._groupOrderRules.push({ ordering : ordering, desc : desc }); + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/axis/methods/addOrderRule.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-addOrderRule + this.addOrderRule = function (ordering, desc) { + this._orderRules.push({ ordering : ordering, desc : desc }); + }; + }; + // End dimple.axis + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/begin.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart + dimple.chart = function (svg, data) { + + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-svg + this.svg = svg; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-x + this.x = "10%"; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-y + this.y = "10%"; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-width + this.width = "80%"; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-height + this.height = "80%"; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-data + this.data = data; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-noFormats + this.noFormats = false; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-axes + this.axes = []; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-series + this.series = []; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-legends + this.legends = []; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-storyboard + this.storyboard = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-titleShape + this.titleShape = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-shapes + this.shapes = null; + + // The group within which to put all of this chart's objects + this._group = svg.append("g"); + // The group within which to put tooltips. This is not initialised here because + // the group would end up behind other chart contents in a multi chart output. It will + // therefore be added and removed by the mouse enter/leave events + this._tooltipGroup = null; + // Colors assigned to chart contents. E.g. a series value. + this._assignedColors = {}; + // The next colour index to use, this value is cycled around for all default colours + this._nextColor = 0; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/_axisIndex.js + // Return the ordinal value of the passed axis. If an orientation is passed, return the order for the + // specific orientation, otherwise return the order from all axes. Returns -1 if the passed axis isn't part of the collection + this._axisIndex = function (axis, orientation) { + + var i = 0, + axisCount = 0, + index = -1; + + for (i = 0; i < this.axes.length; i += 1) { + if (this.axes[i] === axis) { + index = axisCount; + break; + } + if (orientation === null || orientation === undefined || orientation[0] === this.axes[i].position[0]) { + axisCount += 1; + } + } + + return index; + + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/_getSeriesData.js + // Create a dataset containing positioning information for every series + this._getSeriesData = function () { + // If there are series + if (this.series !== null && this.series !== undefined) { + // Iterate all the series + this.series.forEach(function (series) { + // The data for this series + var returnData = [], + // Handle multiple category values by iterating the fields in the row and concatonate the values + // This is repeated for each axis using a small anon function + getField = function (axis, row) { + var returnField = []; + if (axis !== null) { + if (axis._hasTimeField()) { + returnField.push(axis._parseDate(row[axis.timeField])); + } else if (axis._hasCategories()) { + axis.categoryFields.forEach(function (cat) { + returnField.push(row[cat]); + }, this); + } + } + return returnField; + }, + // Catch a non-numeric value and re-calc as count + useCount = { x: false, y: false, z: false, c: false }, + // If the elements are grouped a unique list of secondary category values will be required + secondaryElements = { x: [], y: [] }, + // Get the x and y totals for percentages. This cannot be done in the loop above as we need the data aggregated before we get an abs total. + // otherwise it will wrongly account for negatives and positives rolled together. + totals = { x: [], y: [], z: [] }, + colorBounds = { min: null, max: null }, + tot, + running = { x: [], y: [], z: [] }, + addedCats = [], + catTotals = {}, + grandTotals = { x: 0, y: 0, z: 0 }, + key, + storyCat = "", + orderedStoryboardArray = [], + seriesCat = "", + orderedSeriesArray = [], + xCat = "", + xSortArray = [], + yCat = "", + ySortArray = [], + rules = [], + sortedData = this.data, + groupRules = []; + + if (this.storyboard !== null && this.storyboard !== undefined && this.storyboard.categoryFields.length > 0) { + storyCat = this.storyboard.categoryFields[0]; + orderedStoryboardArray = dimple._getOrderedList(this.data, storyCat, this.storyboard._orderRules); + } + + // Deal with mekkos + if (series.x._hasCategories() && series.x._hasMeasure()) { + xCat = series.x.categoryFields[0]; + xSortArray = dimple._getOrderedList(this.data, xCat, series.x._orderRules.concat([{ ordering : series.x.measure, desc : true }])); + } + if (series.y._hasCategories() && series.y._hasMeasure()) { + yCat = series.y.categoryFields[0]; + ySortArray = dimple._getOrderedList(this.data, yCat, series.y._orderRules.concat([{ ordering : series.y.measure, desc : true }])); + } + + if (series.categoryFields !== null && series.categoryFields !== undefined && series.categoryFields.length > 0) { + // Concat is used here to break the reference to the parent array, if we don't do this, in a storyboarded chart, + // the series rules to grow and grow until the system grinds to a halt trying to deal with them all. + rules = [].concat(series._orderRules); + seriesCat = series.categoryFields[0]; + if (series.c !== null && series.c !== undefined && series.c._hasMeasure()) { + rules.push({ ordering : series.c.measure, desc : true }); + } else if (series.z !== null && series.z !== undefined && series.z._hasMeasure()) { + rules.push({ ordering : series.z.measure, desc : true }); + } else if (series.x._hasMeasure()) { + rules.push({ ordering : series.x.measure, desc : true }); + } else if (series.y._hasMeasure()) { + rules.push({ ordering : series.y.measure, desc : true }); + } + orderedSeriesArray = dimple._getOrderedList(this.data, seriesCat, rules); + } + + sortedData.sort(function (a, b) { + var returnValue = 0; + if (storyCat !== "") { + returnValue = orderedStoryboardArray.indexOf(a[storyCat]) - orderedStoryboardArray.indexOf(b[storyCat]); + } + if (xCat !== "" && returnValue === 0) { + returnValue = xSortArray.indexOf(a[xCat]) - xSortArray.indexOf(b[xCat]); + } + if (yCat !== "" && returnValue === 0) { + returnValue = ySortArray.indexOf(a[yCat]) - ySortArray.indexOf(b[yCat]); + } + if (seriesCat !== "" && returnValue === 0) { + returnValue = orderedSeriesArray.indexOf(a[seriesCat]) - orderedSeriesArray.indexOf(b[seriesCat]); + } + return returnValue; + }); + + // Iterate every row in the data to calculate the return values + sortedData.forEach(function (d) { + // Reset the index + var foundIndex = -1, + xField = getField(series.x, d), + yField = getField(series.y, d), + zField = getField(series.z, d), + // Get the aggregate field using the other fields if necessary + aggField = [], + key, + k, + newRow, + updateData; + if (series.categoryFields === null || series.categoryFields === undefined || series.categoryFields.length === 0) { + aggField = ["All"]; + } else if (series.categoryFields.length === 1 && d[series.categoryFields[0]] === undefined) { + aggField = [series.categoryFields[0]]; + } else { + series.categoryFields.forEach(function (cat) { + aggField.push(d[cat]); + }, this); + } + // Add a key + key = aggField.join("/") + "_" + xField.join("/") + "_" + yField.join("/") + "_" + zField.join("/"); + // See if this field has already been added. + for (k = 0; k < returnData.length; k += 1) { + if (returnData[k].key === key) { + foundIndex = k; + break; + } + } + // If the field was not added, do so here + if (foundIndex === -1) { + newRow = { + key: key, + aggField: aggField, + xField: xField, + xValue: null, + xCount: 0, + yField: yField, + yValue: null, + yCount: 0, + zField: zField, + zValue: null, + zCount: 0, + cValue: 0, + cCount: 0, + x: 0, + y: 0, + xOffset: 0, + yOffset: 0, + width: 0, + height: 0, + cx: 0, + cy: 0, + xBound: 0, + yBound: 0, + xValueList: [], + yValueList: [], + zValueList: [], + cValueList: [], + fill: {}, + stroke: {} + }; + returnData.push(newRow); + foundIndex = returnData.length - 1; + } + + // Update the return data for the passed axis + updateData = function (axis, storyboard) { + var passStoryCheck = true, + lhs = { value: 0, count: 1 }, + rhs = { value: 0, count: 1 }, + selectStoryValue, + compare = "", + retRow; + if (storyboard !== null) { + selectStoryValue = storyboard.getFrameValue(); + storyboard.categoryFields.forEach(function (cat, m) { + if (m > 0) { + compare += "/"; + } + compare += d[cat]; + passStoryCheck = (compare === selectStoryValue); + }, this); + } + if (axis !== null && axis !== undefined) { + if (passStoryCheck) { + retRow = returnData[foundIndex]; + if (axis._hasMeasure()) { + // Treat undefined values as zero + if (d[axis.measure] === undefined) { + d[axis.measure] = 0; + } + // Keep a distinct list of values to calculate a distinct count in the case of a non-numeric value being found + if (retRow[axis.position + "ValueList"].indexOf(d[axis.measure]) === -1) { + retRow[axis.position + "ValueList"].push(d[axis.measure]); + } + // The code above is outside this check for non-numeric values because we might encounter one far down the list, and + // want to have a record of all values so far. + if (isNaN(parseFloat(d[axis.measure]))) { + useCount[axis.position] = true; + } + // Set the value using the aggregate function method + lhs.value = retRow[axis.position + "Value"]; + lhs.count = retRow[axis.position + "Count"]; + rhs.value = d[axis.measure]; + retRow[axis.position + "Value"] = series.aggregate(lhs, rhs); + retRow[axis.position + "Count"] += 1; + } + } + } + }; + // Update all the axes + updateData(series.x, this.storyboard); + updateData(series.y, this.storyboard); + updateData(series.z, this.storyboard); + updateData(series.c, this.storyboard); + }, this); + // Get secondary elements if necessary + if (series.x !== null && series.x !== undefined && series.x._hasCategories() && series.x.categoryFields.length > 1 && secondaryElements.x !== undefined) { + groupRules = []; + if (series.y._hasMeasure()) { + groupRules.push({ ordering : series.y.measure, desc : true }); + } + secondaryElements.x = dimple._getOrderedList(this.data, series.x.categoryFields[1], series.x._groupOrderRules.concat(groupRules)); + } + if (series.y !== null && series.y !== undefined && series.y._hasCategories() && series.y.categoryFields.length > 1 && secondaryElements.y !== undefined) { + groupRules = []; + if (series.x._hasMeasure()) { + groupRules.push({ ordering : series.x.measure, desc : true }); + } + secondaryElements.y = dimple._getOrderedList(this.data, series.y.categoryFields[1], series.y._groupOrderRules.concat(groupRules)); + secondaryElements.y.reverse(); + } + returnData.forEach(function (ret) { + if (series.x !== null) { + if (useCount.x === true) { ret.xValue = ret.xValueList.length; } + tot = (totals.x[ret.xField.join("/")] === null || totals.x[ret.xField.join("/")] === undefined ? 0 : totals.x[ret.xField.join("/")]) + (series.y._hasMeasure() ? Math.abs(ret.yValue) : 0); + totals.x[ret.xField.join("/")] = tot; + } + if (series.y !== null) { + if (useCount.y === true) { ret.yValue = ret.yValueList.length; } + tot = (totals.y[ret.yField.join("/")] === null || totals.y[ret.yField.join("/")] === undefined ? 0 : totals.y[ret.yField.join("/")]) + (series.x._hasMeasure() ? Math.abs(ret.xValue) : 0); + totals.y[ret.yField.join("/")] = tot; + } + if (series.z !== null) { + if (useCount.z === true) { ret.zValue = ret.zValueList.length; } + tot = (totals.z[ret.zField.join("/")] === null || totals.z[ret.zField.join("/")] === undefined ? 0 : totals.z[ret.zField.join("/")]) + (series.z._hasMeasure() ? Math.abs(ret.zValue) : 0); + totals.z[ret.zField.join("/")] = tot; + } + if (series.c !== null) { + if (colorBounds.min === null || ret.cValue < colorBounds.min) { colorBounds.min = ret.cValue; } + if (colorBounds.max === null || ret.cValue > colorBounds.max) { colorBounds.max = ret.cValue; } + } + }, this); + // Before calculating the positions we need to sort elements + + // Set all the dimension properties of the data + for (key in totals.x) { if (totals.x.hasOwnProperty(key)) { grandTotals.x += totals.x[key]; } } + for (key in totals.y) { if (totals.y.hasOwnProperty(key)) { grandTotals.y += totals.y[key]; } } + for (key in totals.z) { if (totals.z.hasOwnProperty(key)) { grandTotals.z += totals.z[key]; } } + + returnData.forEach(function (ret) { + var baseColor, + targetColor, + scale, + colorVal, + floatingPortion, + getAxisData = function (axis, opp, size) { + var totalField, + value, + selectValue, + pos, + cumValue; + if (axis !== null && axis !== undefined) { + pos = axis.position; + if (!axis._hasCategories()) { + value = (axis.showPercent ? ret[pos + "Value"] / totals[opp][ret[opp + "Field"].join("/")] : ret[pos + "Value"]); + totalField = ret[opp + "Field"].join("/") + (ret[pos + "Value"] >= 0); + cumValue = running[pos][totalField] = ((running[pos][totalField] === null || running[pos][totalField] === undefined || pos === "z") ? 0 : running[pos][totalField]) + value; + selectValue = ret[pos + "Bound"] = ret["c" + pos] = (((pos === "x" || pos === "y") && series.stacked) ? cumValue : value); + ret[size] = value; + ret[pos] = selectValue - (((pos === "x" && value >= 0) || (pos === "y" && value <= 0)) ? value : 0); + } else { + if (axis._hasMeasure()) { + totalField = ret[axis.position + "Field"].join("/"); + value = (axis.showPercent ? totals[axis.position][totalField] / grandTotals[axis.position] : totals[axis.position][totalField]); + if (addedCats.indexOf(totalField) === -1) { + catTotals[totalField] = value + (addedCats.length > 0 ? catTotals[addedCats[addedCats.length - 1]] : 0); + addedCats.push(totalField); + } + selectValue = ret[pos + "Bound"] = ret["c" + pos] = (((pos === "x" || pos === "y") && series.stacked) ? catTotals[totalField] : value); + ret[size] = value; + ret[pos] = selectValue - (((pos === "x" && value >= 0) || (pos === "y" && value <= 0)) ? value : 0); + } else { + ret[pos] = ret["c" + pos] = ret[pos + "Field"][0]; + ret[size] = 1; + if (secondaryElements[pos] !== undefined && secondaryElements[pos] !== null && secondaryElements[pos].length >= 2) { + ret[pos + "Offset"] = secondaryElements[pos].indexOf(ret[pos + "Field"][1]); + ret[size] = 1 / secondaryElements[pos].length; + } + } + } + } + }; + getAxisData(series.x, "y", "width"); + getAxisData(series.y, "x", "height"); + getAxisData(series.z, "z", "r"); + + // If there is a color axis + if (series.c !== null && colorBounds.min !== null && colorBounds.max !== null) { + // Handle matching min and max + if (colorBounds.min === colorBounds.max) { + colorBounds.min -= 0.5; + colorBounds.max += 0.5; + } + // Limit the bounds of the color value to be within the range. Users may override the axis bounds and this + // allows a 2 color scale rather than blending if the min and max are set to 0 and 0.01 for example negative values + // and zero value would be 1 color and positive another. + colorBounds.min = (series.c.overrideMin !== null && series.c.overrideMin !== undefined ? series.c.overrideMin : colorBounds.min); + colorBounds.max = (series.c.overrideMax !== null && series.c.overrideMax !== undefined ? series.c.overrideMax : colorBounds.max); + ret.cValue = (ret.cValue > colorBounds.max ? colorBounds.max : (ret.cValue < colorBounds.min ? colorBounds.min : ret.cValue)); + // Calculate the factors for the calculations + scale = d3.scale.linear().range([0, (series.c.colors === null || series.c.colors.length === 1 ? 1 : series.c.colors.length - 1)]).domain([colorBounds.min, colorBounds.max]); + colorVal = scale(ret.cValue); + floatingPortion = colorVal - Math.floor(colorVal); + if (ret.cValue === colorBounds.max) { + floatingPortion = 1; + } + // If there is a single color defined + if (series.c.colors !== null && series.c.colors !== undefined && series.c.colors.length === 1) { + baseColor = d3.rgb(series.c.colors[0]); + targetColor = d3.rgb(this.getColor(ret.aggField.slice(-1)[0]).fill); + } else if (series.c.colors !== null && series.c.colors !== undefined && series.c.colors.length > 1) { + baseColor = d3.rgb(series.c.colors[Math.floor(colorVal)]); + targetColor = d3.rgb(series.c.colors[Math.ceil(colorVal)]); + } else { + baseColor = d3.rgb("white"); + targetColor = d3.rgb(this.getColor(ret.aggField.slice(-1)[0]).fill); + } + // Calculate the correct grade of color + baseColor.r = Math.floor(baseColor.r + (targetColor.r - baseColor.r) * floatingPortion); + baseColor.g = Math.floor(baseColor.g + (targetColor.g - baseColor.g) * floatingPortion); + baseColor.b = Math.floor(baseColor.b + (targetColor.b - baseColor.b) * floatingPortion); + // Set the colors on the row + ret.fill = baseColor.toString(); + ret.stroke = baseColor.darker(0.5).toString(); + } + + }, this); + + // populate the data in the series + series._positionData = returnData; + + }, this); + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/_heightPixels.js + // Access the pixel value of the height of the plot area + this._heightPixels = function () { + return dimple._parseYPosition(this.height, this.svg.node()); + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/_registerEventHandlers.js + // Register events, handle standard d3 shape events + this._registerEventHandlers = function (series) { + if (series._eventHandlers !== null && series._eventHandlers.length > 0) { + series._eventHandlers.forEach(function (thisHandler) { + if (thisHandler.handler !== null && typeof (thisHandler.handler) === "function") { + series.shapes.on(thisHandler.event, function (d) { + var e = new dimple.eventArgs(); + if (series.chart.storyboard !== null) { + e.frameValue = series.chart.storyboard.getFrameValue(); + } + e.seriesValue = d.aggField; + e.xValue = d.x; + e.yValue = d.y; + e.zValue = d.z; + e.colorValue = d.cValue; + e.seriesShapes = series.shapes; + e.selectedShape = d3.select(this); + thisHandler.handler(e); + }); + } + }, this); + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/_widthPixels.js + // Access the pixel value of the width of the plot area + this._widthPixels = function () { + return dimple._parseXPosition(this.width, this.svg.node()); + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/_xPixels.js + // Access the pixel position of the x co-ordinate of the plot area + this._xPixels = function () { + return dimple._parseXPosition(this.x, this.svg.node()); + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/_yPixels.js + // Access the pixel position of the y co-ordinate of the plot area + this._yPixels = function () { + return dimple._parseYPosition(this.y, this.svg.node()); + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/addAxis.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-addAxis + this.addAxis = function (position, categoryFields, measure, timeField) { + // Convert the passed category fields to an array in case a single string is sent + if (categoryFields !== null && categoryFields !== undefined) { + categoryFields = [].concat(categoryFields); + } + // Create the axis object based on the passed parameters + var axis = new dimple.axis( + this, + position, + categoryFields, + measure, + timeField + ); + // Add the axis to the array for the chart + this.axes.push(axis); + // return the axis + return axis; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/addCategoryAxis.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-addCategoryAxis + this.addCategoryAxis = function (position, categoryFields) { + return this.addAxis(position, categoryFields, null); + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/addColorAxis.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-addColorAxis + this.addColorAxis = function (measure, colors) { + var colorAxis = this.addAxis("c", null, measure); + colorAxis.colors = (colors === null || colors === undefined ? null : [].concat(colors)); + return colorAxis; + }; + + + // Source: /src/objects/chart/methods/addLegend.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-addLegend + this.addLegend = function (x, y, width, height, horizontalAlign, series) { + // Use all series by default + series = (series === null || series === undefined ? this.series : [].concat(series)); + horizontalAlign = (horizontalAlign === null || horizontalAlign === undefined ? "left" : horizontalAlign); + // Create the legend + var legend = new dimple.legend(this, x, y, width, height, horizontalAlign, series); + // Add the legend to the array + this.legends.push(legend); + // Return the legend object + return legend; + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/addLogAxis.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-addLogAxis + this.addLogAxis = function (position, logField, logBase) { + var axis = this.addAxis(position, null, logField, null); + if (logBase !== null && logBase !== undefined) { + axis.logBase = logBase; + } + axis.useLog = true; + return axis; + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/addMeasureAxis.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-addMeasureAxis + this.addMeasureAxis = function (position, measure) { + return this.addAxis(position, null, measure); + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/addPctAxis.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-addPctAxis + this.addPctAxis = function (position, measure, categories) { + var pctAxis = null; + if (categories !== null && categories !== undefined) { + pctAxis = this.addAxis(position, categories, measure); + } else { + pctAxis = this.addMeasureAxis(position, measure); + } + pctAxis.showPercent = true; + return pctAxis; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/addSeries.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-addSeries + this.addSeries = function (categoryFields, plotFunction, axes) { + // Deal with no axes passed + if (axes === null || axes === undefined) { axes = this.axes; } + // Deal with no plot function + if (plotFunction === null || plotFunction === undefined) { plotFunction = dimple.plot.bubble; } + // Axis objects to be picked from the array + var xAxis = null, + yAxis = null, + zAxis = null, + colorAxis = null, + series; + // Iterate the array and pull out the relevant axes + axes.forEach(function (axis) { + if (axis !== null && plotFunction.supportedAxes.indexOf(axis.position) > -1) { + if (xAxis === null && axis.position[0] === "x") { + xAxis = axis; + } else if (yAxis === null && axis.position[0] === "y") { + yAxis = axis; + } else if (zAxis === null && axis.position[0] === "z") { + zAxis = axis; + } else if (colorAxis === null && axis.position[0] === "c") { + colorAxis = axis; + } + } + }, this); + // Put single values into single value arrays + if (categoryFields !== null && categoryFields !== undefined) { + categoryFields = [].concat(categoryFields); + } + // Create a series object + series = new dimple.series( + this, + categoryFields, + xAxis, + yAxis, + zAxis, + colorAxis, + plotFunction, + dimple.aggregateMethod.sum, + plotFunction.stacked + ); + // Add the series to the chart's array + this.series.push(series); + // Return the series + return series; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/addTimeAxis.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-addTimeAxis + this.addTimeAxis = function (position, timeField, inputFormat, outputFormat) { + var axis = this.addAxis(position, null, null, timeField); + axis.tickFormat = outputFormat; + axis.dateParseFormat = inputFormat; + return axis; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/assignColor.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-assignColor + this.assignColor = function (tag, fill, stroke, opacity) { + this._assignedColors[tag] = new dimple.color(fill, stroke, opacity); + return this._assignedColors[tag]; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/defaultColors.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-defaultColors + this.defaultColors = [ + new dimple.color("#80B1D3"), // Blue + new dimple.color("#FB8072"), // Red + new dimple.color("#FDB462"), // Orange + new dimple.color("#B3DE69"), // Green + new dimple.color("#FFED6F"), // Yellow + new dimple.color("#BC80BD"), // Purple + new dimple.color("#8DD3C7"), // Turquoise + new dimple.color("#CCEBC5"), // Pale Blue + new dimple.color("#FFFFB3"), // Pale Yellow + new dimple.color("#BEBADA"), // Lavender + new dimple.color("#FCCDE5"), // Pink + new dimple.color("#D9D9D9") // Grey + ]; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/draw.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-draw + this.draw = function (duration, noDataChange) { + // Deal with optional parameter + duration = (duration === null || duration === undefined ? 0 : duration); + // Catch the first x and y + var firstX = null, + firstY = null, + distinctCats, + xGridSet = false, + yGridSet = false, + chartX = this._xPixels(), + chartY = this._yPixels(), + chartWidth = this._widthPixels(), + chartHeight = this._heightPixels(); + + // Many of the draw methods use positioning data in each series. Therefore we should + // decorate the series with it now + if (noDataChange === undefined || noDataChange === null || noDataChange === false) { + this._getSeriesData(); + } + + // Iterate the axes and calculate bounds, this is done within the chart because an + // axis' bounds are determined by other axes and the way that series tie them together + this.axes.forEach(function (axis) { + axis._min = 0; + axis._max = 0; + // Check that the axis has a measure + if (axis._hasMeasure()) { + // Is this axis linked to a series + var linked = false; + // Find any linked series + this.series.forEach(function (series) { + // if this axis is linked + if (series[axis.position] === axis) { + // Get the bounds + var bounds = series._axisBounds(axis.position); + if (axis._min > bounds.min) { axis._min = bounds.min; } + if (axis._max < bounds.max) { axis._max = bounds.max; } + linked = true; + } + }, this); + // If the axis is not linked, use the data bounds, this is unlikely to be used + // in a real context, but when developing it is nice to see axes before any series have + // been added. + if (!linked) { + this.data.forEach(function (d) { + if (axis._min > d[axis.measure]) { axis._min = d[axis.measure]; } + if (axis._max < d[axis.measure]) { axis._max = d[axis.measure]; } + }, this); + } + } else if (axis._hasTimeField()) { + // Parse the dates and assign the min and max + axis._min = null; + axis._max = null; + this.data.forEach(function (d) { + var dt = axis._parseDate(d[axis.timeField]); + if (axis._min === null || dt < axis._min) { + axis._min = dt; + } + if (axis._max === null || dt > axis._max) { + axis._max = dt; + } + }, this); + } else if (axis._hasCategories()) { + // A category axis is just set to show the number of categories + axis._min = 0; + distinctCats = []; + this.data.forEach(function (d) { + if (distinctCats.indexOf(d[axis.categoryFields[0]]) === -1) { + distinctCats.push(d[axis.categoryFields[0]]); + } + }, this); + axis._max = distinctCats.length; + } + + + // Update the axis now we have all information set + axis._update(); + + // Record the index of the first x and first y axes + if (firstX === null && axis.position === "x") { + firstX = axis; + } else if (firstY === null && axis.position === "y") { + firstY = axis; + } + }, this); + // Iterate the axes again + this.axes.forEach(function (axis) { + // Don't animate axes on first draw + var firstDraw = false, + transform = null, + gridSize = 0, + gridTransform = null, + handleTrans, + rotated = false, + widest = 0, + box = { l: null, t: null, r: null, b: null }, + titleX = 0, + titleY = 0, + rotate = "", + chart = this; + if (axis.gridlineShapes === null) { + if (axis.showGridlines || (axis.showGridlines === null && !axis._hasCategories() && ((!xGridSet && axis.position === "x") || (!yGridSet && axis.position === "y")))) { + // Add a group for the gridlines to allow css formatting + axis.gridlineShapes = this._group.append("g").attr("class", "gridlines"); + if (axis.position === "x") { + xGridSet = true; + } else { + yGridSet = true; + } + } + } else { + if (axis.position === "x") { + xGridSet = true; + } else { + yGridSet = true; + } + } + if (axis.shapes === null) { + // Add a group for the axes to allow css formatting + axis.shapes = this._group.append("g").attr("class", "axis"); + firstDraw = true; + } + // If this is the first x and there is a y axis cross them at zero + if (axis === firstX && firstY !== null) { + transform = "translate(0, " + (firstY.categoryFields === null || firstY.categoryFields.length === 0 ? firstY._scale(0) : chartY + chartHeight) + ")"; + gridTransform = "translate(0, " + (axis === firstX ? chartY + chartHeight : chartY) + ")"; + gridSize = -chartHeight; + } else if (axis === firstY && firstX !== null) { + transform = "translate(" + (firstX.categoryFields === null || firstX.categoryFields.length === 0 ? firstX._scale(0) : chartX) + ", 0)"; + gridTransform = "translate(" + (axis === firstY ? chartX : chartX + chartWidth) + ", 0)"; + gridSize = -chartWidth; + } else if (axis.position === "x") { + gridTransform = transform = "translate(0, " + (axis === firstX ? chartY + chartHeight : chartY) + ")"; + gridSize = -chartHeight; + } else if (axis.position === "y") { + gridTransform = transform = "translate(" + (axis === firstY ? chartX : chartX + chartWidth) + ", 0)"; + gridSize = -chartWidth; + } + // Draw the axis + // This code might seem unneccesary but even applying a duration of 0 to a transition will cause the code to execute after the + // code below and precedence is important here. + handleTrans = function (ob) { + var returnObj; + if (transform === null || duration === 0 || firstDraw) { + returnObj = ob; + } else { + returnObj = ob.transition().duration(duration); + } + return returnObj; + }; + if (transform !== null && axis._draw !== null) { + + // Add a tick format + if (axis._hasTimeField()) { + handleTrans(axis.shapes).call(axis._draw.ticks(axis._getTimePeriod(), axis.timeInterval).tickFormat(axis._getFormat())).attr("transform", transform); + } else if (axis.useLog) { + handleTrans(axis.shapes).call(axis._draw.ticks(4, axis._getFormat())).attr("transform", transform); + } else { + handleTrans(axis.shapes).call(axis._draw.tickFormat(axis._getFormat())).attr("transform", transform); + } + if (axis.gridlineShapes !== null) { + handleTrans(axis.gridlineShapes).call(axis._draw.tickSize(gridSize, 0, 0).tickFormat("")).attr("transform", gridTransform); + } + // Move labels around + if (axis.measure === null || axis.measure === undefined) { + if (axis.position === "x") { + handleTrans(axis.shapes.selectAll("text")).attr("x", (chartWidth / axis._max) / 2); + } else if (axis.position === "y") { + handleTrans(axis.shapes.selectAll("text")).attr("y", -1 * (chartHeight / axis._max) / 2); + } + } + if (axis.categoryFields !== null && axis.categoryFields !== undefined && axis.categoryFields.length > 0) { + // Off set the labels to counter the transform. This will put the labels along the outside of the chart so they + // don't interfere with the chart contents + if (axis === firstX && (firstY.categoryFields === null || firstY.categoryFields.length === 0)) { + handleTrans(axis.shapes.selectAll("text")).attr("y", chartY + chartHeight - firstY._scale(0) + 9); + } + if (axis === firstY && (firstX.categoryFields === null || firstX.categoryFields.length === 0)) { + handleTrans(axis.shapes.selectAll("text")).attr("x", -1 * (firstX._scale(0) - chartX) - 9); + } + } + } + // Set some initial css values + if (!this.noFormats) { + handleTrans(axis.shapes.selectAll("text")) + .style("font-family", "sans-serif") + .style("font-size", (chartHeight / 35 > 10 ? chartHeight / 35 : 10) + "px"); + handleTrans(axis.shapes.selectAll("path, line")) + .style("fill", "none") + .style("stroke", "black") + .style("shape-rendering", "crispEdges"); + if (axis.gridlineShapes !== null) { + handleTrans(axis.gridlineShapes.selectAll("line")) + .style("fill", "none") + .style("stroke", "lightgray") + .style("opacity", 0.8); + } + } + // Rotate labels, this can only be done once the formats are set + if (axis.measure === null || axis.measure === undefined) { + if (axis === firstX) { + // If the gaps are narrower than the widest label display all labels horizontally + widest = 0; + axis.shapes.selectAll("text").each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > chartWidth / axis.shapes.selectAll("text")[0].length) { + rotated = true; + axis.shapes.selectAll("text") + .style("text-anchor", "start") + .each(function () { + var rec = this.getBBox(); + d3.select(this) + .attr("transform", "rotate(90," + rec.x + "," + (rec.y + (rec.height / 2)) + ") translate(-5, 0)"); + }); + } else { + // For redraw operations we need to clear the transform + rotated = false; + axis.shapes.selectAll("text") + .style("text-anchor", "middle") + .attr("transform", ""); + } + } else if (axis.position === "x") { + // If the gaps are narrower than the widest label display all labels horizontally + widest = 0; + axis.shapes.selectAll("text") + .each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > chartWidth / axis.shapes.selectAll("text")[0].length) { + rotated = true; + axis.shapes.selectAll("text") + .style("text-anchor", "end") + .each(function () { + var rec = this.getBBox(); + d3.select(this) + .attr("transform", "rotate(90," + (rec.x + rec.width) + "," + (rec.y + (rec.height / 2)) + ") translate(5, 0)"); + }); + } else { + // For redraw operations we need to clear the transform + rotated = false; + axis.shapes.selectAll("text") + .style("text-anchor", "middle") + .attr("transform", ""); + } + } + } + if (axis.titleShape !== null && axis.titleShape !== undefined) { + axis.titleShape.remove(); + } + // Get the bounds of the axis objects + axis.shapes.selectAll("text") + .each(function () { + var rec = this.getBBox(); + if (box.l === null || -9 - rec.width < box.l) { + box.l = -9 - rec.width; + } + if (box.r === null || rec.x + rec.width > box.r) { + box.r = rec.x + rec.width; + } + if (rotated) { + if (box.t === null || rec.y + rec.height - rec.width < box.t) { + box.t = rec.y + rec.height - rec.width; + } + if (box.b === null || rec.height + rec.width > box.b) { + box.b = rec.height + rec.width; + } + } else { + if (box.t === null || rec.y < box.t) { + box.t = rec.y; + } + if (box.b === null || 9 + rec.height > box.b) { + box.b = 9 + rec.height; + } + } + }); + + if (axis.position === "x") { + if (axis === firstX) { + titleY = chartY + chartHeight + box.b + 5; + } else { + titleY = chartY + box.t - 10; + } + titleX = chartX + (chartWidth / 2); + } else if (axis.position === "y") { + if (axis === firstY) { + titleX = chartX + box.l - 10; + } else { + titleX = chartX + chartWidth + box.r + 20; + } + titleY = chartY + (chartHeight / 2); + rotate = "rotate(270, " + titleX + ", " + titleY + ")"; + } + + // Add a title for the axis + if (!axis.hidden && (axis.position === "x" || axis.position === "y")) { + axis.titleShape = this._group.append("text").attr("class", "axis title"); + axis.titleShape + .attr("x", titleX) + .attr("y", titleY) + .attr("text-anchor", "middle") + .attr("transform", rotate) + .text((axis.categoryFields === null || axis.categoryFields === undefined || axis.categoryFields.length === 0 ? axis.measure : axis.categoryFields.join("/"))) + .each(function () { + if (!chart.noFormats) { + d3.select(this) + .style("font-family", "sans-serif") + .style("font-size", (chartHeight / 35 > 10 ? chartHeight / 35 : 10) + "px"); + } + }); + + // Offset Y position to baseline. This previously used dominant-baseline but this caused + // browser inconsistency + if (axis === firstX) { + axis.titleShape.each(function () { + d3.select(this).attr("y", titleY + this.getBBox().height / 1.65); + }); + } else if (axis === firstY) { + axis.titleShape.each(function () { + d3.select(this).attr("x", titleX + this.getBBox().height / 1.65); + }); + } + } + // } + }, this); + + // Iterate the series + this.series.forEach(function (series) { + series.plot.draw(this, series, duration); + this._registerEventHandlers(series); + }, this); + + // Iterate the legends + this.legends.forEach(function (legend) { + legend._draw(duration); + }, this); + + // If the chart has a storyboard + if (this.storyboard !== null && this.storyboard !== undefined) { + this.storyboard._drawText(); + if (this.storyboard.autoplay) { + this.storyboard.startAnimation(); + } + } + + // Return the chart for chaining + return this; + + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/getColor.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-getColor + this.getColor = function (tag) { + // If no color is assigned, do so here + if (this._assignedColors[tag] === null || this._assignedColors[tag] === undefined) { + this._assignedColors[tag] = this.defaultColors[this._nextColor]; + this._nextColor = (this._nextColor + 1) % this.defaultColors.length; + } + // Return the color + return this._assignedColors[tag]; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/setBounds.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-setBounds + this.setBounds = function (x, y, width, height) { + // Store the passed parameters + this.x = x; + this.y = y; + this.width = width; + this.height = height; + // Access the pixel value of the x coordinate + this._xPixels = function () { + return dimple._parseXPosition(this.x, this.svg.node()); + }; + // Access the pixel value of the y coordinate + this._yPixels = function () { + return dimple._parseYPosition(this.y, this.svg.node()); + }; + // Access the pixel value of the width coordinate + this._widthPixels = function () { + return dimple._parseXPosition(this.width, this.svg.node()); + }; + // Access the pixel value of the width coordinate + this._heightPixels = function () { + return dimple._parseYPosition(this.height, this.svg.node()); + }; + // Refresh the axes to redraw them against the new bounds + this.draw(0, true); + // return the chart object for method chaining + return this; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/setMargins.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-setMargins + this.setMargins = function (left, top, right, bottom) { + // Set the bounds here, functions below will be used for access + this.x = left; + this.y = top; + this.width = 0; + this.height = 0; + // Access the pixel value of the x coordinate + this._xPixels = function () { + return dimple._parseXPosition(this.x, this.svg.node()); + }; + // Access the pixel value of the y coordinate + this._yPixels = function () { + return dimple._parseYPosition(this.y, this.svg.node()); + }; + // Access the pixel value of the width coordinate + this._widthPixels = function () { + return dimple._parentWidth(this.svg.node()) - this._xPixels() - dimple._parseXPosition(right, this.svg.node()); + }; + // Access the pixel value of the width coordinate + this._heightPixels = function () { + return dimple._parentHeight(this.svg.node()) - this._yPixels() - dimple._parseYPosition(bottom, this.svg.node()); + }; + // Refresh the axes to redraw them against the new bounds + this.draw(0, true); + // return the chart object for method chaining + return this; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/setStoryboard.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-setStoryboard + this.setStoryboard = function (categoryFields, tickHandler) { + // Create and assign the storyboard + this.storyboard = new dimple.storyboard(this, categoryFields); + // Set the event handler + if (tickHandler !== null && tickHandler !== undefined) { + this.storyboard.onTick = tickHandler; + } + // Return the storyboard + return this.storyboard; + }; + + }; + // End dimple.chart + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/color/begin.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.color + dimple.color = function (fill, stroke, opacity) { + + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.color#wiki-fill + this.fill = fill; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.color#wiki-stroke + this.stroke = (stroke === null || stroke === undefined ? d3.rgb(fill).darker(0.5).toString() : stroke); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.color#wiki-opacity + this.opacity = (opacity === null || opacity === undefined ? 0.8 : opacity); + + }; + // End dimple.color + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/eventArgs/begin.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.eventArgs + dimple.eventArgs = function () { + + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.eventArgs#wiki-seriesValue + this.seriesValue = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.eventArgs#wiki-xValue + this.xValue = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.eventArgs#wiki-yValue + this.yValue = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.eventArgs#wiki-zValue + this.zValue = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.eventArgs#wiki-colorValue + this.colorValue = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.eventArgs#wiki-frameValue + this.frameValue = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.eventArgs#wiki-seriesShapes + this.seriesShapes = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.eventArgs#wiki-selectedShape + this.selectedShape = null; + + }; + // End dimple.eventArgs + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/legend/begin.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend + dimple.legend = function (chart, x, y, width, height, horizontalAlign, series) { + + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-chart + this.chart = chart; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-series + this.series = series; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-x + this.x = x; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-y + this.y = y; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-width + this.width = width; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-height + this.height = height; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-horizontalAlign + this.horizontalAlign = horizontalAlign; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-shapes + this.shapes = null; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/legend/methods/_draw.js + // Render the legend + this._draw = function (duration) { + + // Create an array of distinct color elements from the series + var legendArray = this._getEntries(), + maxWidth = 0, + maxHeight = 0, + runningX = 0, + runningY = 0, + keyWidth = 15, + keyHeight = 9, + self = this, + theseShapes; + + // If there is already a legend, fade to transparent and remove + if (this.shapes !== null && this.shapes !== undefined) { + this.shapes + .transition() + .duration(duration * 0.2) + .attr("opacity", 0) + .remove(); + } + + // Create an empty hidden group for every legend entry + // the selector here must not pick up any legend entries being removed by the + // transition above + theseShapes = this.chart._group + .selectAll(".dontSelectAny") + .data(legendArray) + .enter() + .append("g") + .attr("class", "legend") + .attr("opacity", 0); + + // Add text into the group + theseShapes.append("text") + .attr("id", function (d) { return "legend_" + d.key; }) + .attr("class", "legendText") + .text(function(d) { + return d.key; + }) + .call(function () { + if (!self.chart.noFormats) { + this.style("font-family", "sans-serif") + .style("font-size", (self.chart._heightPixels() / 35 > 10 ? self.chart._heightPixels() / 35 : 10) + "px") + .style("shape-rendering", "crispEdges"); + } + }) + .each(function () { + var b = this.getBBox(); + if (b.width > maxWidth) { + maxWidth = b.width; + } + if (b.height > maxHeight) { + maxHeight = b.height; + } + }); + + // Add a rectangle into the group + theseShapes.append("rect") + .attr("class", "legendKey") + .attr("height", keyHeight) + .attr("width", keyWidth); + + // Expand the bounds of the largest shape slightly. This will be the size allocated to + // all elements + maxHeight = (maxHeight < keyHeight ? keyHeight : maxHeight) + 2; + maxWidth += keyWidth + 20; + + // Iterate the shapes and position them based on the alignment and size of the legend + theseShapes + .each(function (d) { + if (runningX + maxWidth > self._widthPixels()) { + runningX = 0; + runningY += maxHeight; + } + if (runningY > self._heightPixels()) { + d3.select(this).remove(); + } else { + d3.select(this).select("text") + .attr("x", (self.horizontalAlign === "left" ? self._xPixels() + keyWidth + 5 + runningX : self._xPixels() + (self._widthPixels() - runningX - maxWidth) + keyWidth + 5)) + .attr("y", function () { + // This was previously done with dominant-baseline but this is used + // instead due to browser inconsistancy. + return self._yPixels() + runningY + this.getBBox().height / 1.65; + }) + .attr("width", self._widthPixels()) + .attr("height", self._heightPixels()); + d3.select(this).select("rect") + .attr("class", "legend legendKey") + .attr("x", (self.horizontalAlign === "left" ? self._xPixels() + runningX : self._xPixels() + (self._widthPixels() - runningX - maxWidth))) + .attr("y", self._yPixels() + runningY) + .attr("height", keyHeight) + .attr("width", keyWidth) + .style("fill", function () { return dimple._helpers.fill(d, self.chart, d.series); }) + .style("stroke", function () { return dimple._helpers.stroke(d, self.chart, d.series); }) + .style("opacity", function () { return dimple._helpers.opacity(d, self.chart, d.series); }) + .style("shape-rendering", "crispEdges"); + runningX += maxWidth; + } + }); + + // Fade in the shapes if this is transitioning + theseShapes + .transition() + .delay(duration * 0.2) + .duration(duration * 0.8) + .attr("opacity", 1); + + // Assign them to the public property for modification by the user. + this.shapes = theseShapes; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/legend/methods/_getEntries.js + // Get an array of elements to be displayed in the legend + this._getEntries = function () { + // Create an array of distinct series values + var entries = []; + // If there are some series + if (this.series !== null && this.series !== undefined) { + // Iterate all the associated series + this.series.forEach(function (series) { + // Get the series data + var data = series._positionData; + // Iterate the aggregated data + data.forEach(function (row) { + // Check whether this element is new + var index = -1, + j; + for (j = 0; j < entries.length; j += 1) { + if (entries[j].key === row.aggField.slice(-1)[0]) { + index = j; + break; + } + } + if (index === -1) { + // If it's a new element create a new row in the return array + entries.push({ key: row.aggField.slice(-1)[0], fill: row.fill, stroke: row.stroke, series: series, aggField: row.aggField }); + index = entries.length - 1; + } + }); + }, this); + } + return entries; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/legend/methods/_heightPixels.js + // Access the pixel value of the height of the legend area + this._heightPixels = function () { + return dimple._parseYPosition(this.height, this.chart.svg.node()); + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/legend/methods/_widthPixels.js + // Access the pixel value of the width of the legend area + this._widthPixels = function () { + return dimple._parseXPosition(this.width, this.chart.svg.node()); + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/legend/methods/_xPixels.js + // Access the pixel position of the x co-ordinate of the legend area + this._xPixels = function () { + return dimple._parseXPosition(this.x, this.chart.svg.node()); + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/legend/methods/_yPixels.js + // Access the pixel position of the y co-ordinate of the legend area + this._yPixels = function () { + return dimple._parseYPosition(this.y, this.chart.svg.node()); + }; + }; + // End dimple.legend + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/series/begin.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series + dimple.series = function (chart, categoryFields, xAxis, yAxis, zAxis, colorAxis, plotFunction, aggregateFunction, stacked) { + + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-chart + this.chart = chart; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-x + this.x = xAxis; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-y + this.y = yAxis; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-z + this.z = zAxis; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-c + this.c = colorAxis; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-plot + this.plot = plotFunction; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-categoryFields + this.categoryFields = categoryFields; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-aggregateFunction + this.aggregate = aggregateFunction; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-stacked + this.stacked = stacked; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-barGap + this.barGap = 0.2; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-clusterBarGap + this.clusterBarGap = 0.1; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-lineWeight + this.lineWeight = 2; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-lineMarkers + this.lineMarkers = false; + + // Any event handlers joined to this series + this._eventHandlers = []; + // The series positioning information + this._positionData = []; + // The order definition array + this._orderRules = []; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/series/methods/_axisBounds.js + this._axisBounds = function (position) { + var bounds = { min: 0, max: 0 }, + // The primary axis for this comparison + primaryAxis = null, + // The secondary axis for this comparison + secondaryAxis = null, + // The running totals of the categories + categoryTotals = [], + // The maximum index of category totals + catCount = 0, + measureName, + fieldName, + distinctCats, + aggData = this._positionData; + + // If the primary axis is x the secondary is y and vice versa, a z axis has no secondary + if (position === "x") { + primaryAxis = this.x; + secondaryAxis = this.y; + } else if (position === "y") { + primaryAxis = this.y; + secondaryAxis = this.x; + } else if (position === "z") { + primaryAxis = this.z; + } else if (position === "c") { + primaryAxis = this.c; + } + + // If the corresponding axis is category axis + if (primaryAxis.showPercent) { + // Iterate the data + aggData.forEach(function (d) { + if (d[primaryAxis.position + "Bound"] < bounds.min) { + bounds.min = d[primaryAxis.position + "Bound"]; + } + if (d[primaryAxis.position + "Bound"] > bounds.max) { + bounds.max = d[primaryAxis.position + "Bound"]; + } + }, this); + } else if (secondaryAxis === null || secondaryAxis.categoryFields === null || secondaryAxis.categoryFields.length === 0) { + aggData.forEach(function (d) { + // If the primary axis is stacked + if (this.stacked && (primaryAxis.position === "x" || primaryAxis.position === "y")) { + // We just need to push the bounds. A stacked axis will always include 0 so I just need to push the min and max out from there + if (d[primaryAxis.position + "Value"] < 0) { + bounds.min = bounds.min + d[primaryAxis.position + "Value"]; + } else { + bounds.max = bounds.max + d[primaryAxis.position + "Value"]; + } + } else { + // If it isn't stacked we need to catch the minimum and maximum values + if (d[primaryAxis.position + "Value"] < bounds.min) { + bounds.min = d[primaryAxis.position + "Value"]; + } + if (d[primaryAxis.position + "Value"] > bounds.max) { + bounds.max = d[primaryAxis.position + "Value"]; + } + } + }, this); + } else { + // If this category value (or combination if multiple fields defined) is not already in the array of categories, add it. + measureName = primaryAxis.position + "Value"; + fieldName = secondaryAxis.position + "Field"; + // Get a list of distinct categories on the secondary axis + distinctCats = []; + aggData.forEach(function (d) { + // Create a field for this row in the aggregated data + var field = d[fieldName].join("/"), + index = distinctCats.indexOf(field); + if (index === -1) { + distinctCats.push(field); + index = distinctCats.length - 1; + } + // Get the index of the field + if (categoryTotals[index] === undefined) { + categoryTotals[index] = { min: 0, max: 0 }; + if (index >= catCount) { + catCount = index + 1; + } + } + // The secondary axis is a category axis, we need to account + // for distribution across categories + if (this.stacked) { + if (d[measureName] < 0) { + categoryTotals[index].min = categoryTotals[index].min + d[measureName]; + } else { + categoryTotals[index].max = categoryTotals[index].max + d[measureName]; + } + } else { + // If it isn't stacked we need to catch the minimum and maximum values + if (d[measureName] < categoryTotals[index].min) { + categoryTotals[index].min = d[measureName]; + } + if (d[measureName] > categoryTotals[index].max) { + categoryTotals[index].max = d[measureName]; + } + } + }, this); + categoryTotals.forEach(function (catTot) { + if (catTot !== undefined) { + if (catTot.min < bounds.min) { + bounds.min = catTot.min; + } + if (catTot.max > bounds.max) { + bounds.max = catTot.max; + } + } + }, this); + } + return bounds; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/series/methods/_dropLineOrigin.js + this._dropLineOrigin = function() { + + // Get the origin co-ordinates for axis drop lines + var xIndex = 0, + yIndex = 0, + // This contains the drop line destinations + coord = { + // The x co-ordinate for a y-axis drop line + x: null, + // The y co-ordinate for an x-axis drop line + y: null + }, + // The origin of the first axes + firstOrig = { + x: null, + y: null + }; + // Get the first x and y first of all + this.chart.axes.forEach(function (axis) { + if (axis.position === "x" && firstOrig.x === null) { + if (axis._hasTimeField()) { + firstOrig.x = this.chart._xPixels(); + } else { + firstOrig.x = axis._origin; + } + } else if (axis.position === "y" && firstOrig.y === null) { + if (axis._hasTimeField()) { + firstOrig.y = this.chart._yPixels() + this.chart._heightPixels(); + } else { + firstOrig.y = axis._origin; + } + } + }, this); + // Get the axis position based on the axis index + this.chart.axes.forEach(function (axis) { + if (axis.position === "x" && !this.x.hidden) { + if (axis === this.x) { + // Set the y co-ordinate for the x axis + if (xIndex === 0) { + coord.y = firstOrig.y; + } else if (xIndex === 1) { + coord.y = this.chart._yPixels(); + } + } + xIndex += 1; + } else if (axis.position === "y" && !this.y.hidden) { + if (axis === this.y) { + // Set the x co-ordinate for the y axis + if (yIndex === 0) { + coord.x = firstOrig.x; + } else if (yIndex === 1) { + coord.x = this.chart._xPixels() + this.chart._widthPixels(); + } + } + yIndex += 1; + } + }, this); + + // Return the co-ordinate + return coord; + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/series/methods/addEventHandler.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-addEventHandler + this.addEventHandler = function (event, handler) { + this._eventHandlers.push({ event: event, handler: handler }); + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/series/methods/addOrderRule.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.series#wiki-addOrderRule + this.addOrderRule = function (ordering, desc) { + this._orderRules.push({ ordering : ordering, desc : desc }); + }; + }; + // End dimple.series + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/begin.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.storyboard + dimple.storyboard = function (chart, categoryFields) { + + // Handle an individual string as an array + if (categoryFields !== null && categoryFields !== undefined) { + categoryFields = [].concat(categoryFields); + } + + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.storyboard#wiki-chart + this.chart = chart; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.storyboard#wiki-categoryFields + this.categoryFields = categoryFields; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.storyboard#wiki-autoplay + this.autoplay = true; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.storyboard#wiki-frameDuration + this.frameDuration = 3000; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.storyboard#wiki-storyLabel + this.storyLabel = null; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.storyboard#wiki-onTick + this.onTick = null; + + // The current frame index + this._frame = 0; + // The animation interval + this._animationTimer = null; + // The category values + this._categories = []; + // The category values when the last cache happened + this._cachedCategoryFields = []; + // The rules for ordering the storyboard + this._orderRules = []; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/methods/drawText.js + this._drawText = function (duration) { + if (this.storyLabel === null || this.storyLabel === undefined) { + var chart = this.chart, + xCount = 0; + // Check for a secondary x axis + this.chart.axes.forEach(function (a) { + if (a.position === "x") { + xCount += 1; + } + }, this); + this.storyLabel = this.chart._group.append("text") + .attr("x", this.chart._xPixels() + this.chart._widthPixels() * 0.01) + .attr("y", this.chart._yPixels() + (this.chart._heightPixels() / 35 > 10 ? this.chart._heightPixels() / 35 : 10) * (xCount > 1 ? 1.25 : -1)) + .call(function () { + if (!chart.noFormats) { + this.style("font-family", "sans-serif") + .style("font-size", (chart._heightPixels() / 35 > 10 ? chart._heightPixels() / 35 : 10) + "px"); + } + }); + } + this.storyLabel + .transition().duration(duration * 0.2) + .attr("opacity", 0); + this.storyLabel + .transition().delay(duration * 0.2) + .text(this.categoryFields.join("\\") + ": " + this.getFrameValue()) + .transition().duration(duration * 0.8) + .attr("opacity", 1); + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/methods/_getCategories.js + this._getCategories = function() { + if (this._categoryFields !== this._cachedCategoryFields) { + // Clear the array + this._categories = []; + // Iterate every row in the data + this.chart.data.forEach(function (d) { + // Initialise the index of the categories array matching the current row + var index = -1, + field = ""; + // If this is a category axis handle multiple category values by iterating the fields in the row and concatonate the values + if (this.categoryFields !== null) { + this.categoryFields.forEach(function (cat, i) { + if (i > 0) { + field += "/"; + } + field += d[cat]; + }, this); + index = this._categories.indexOf(field); + if (index === -1) { + this._categories.push(field); + index = this._categories.length - 1; + } + } + }, this); + // Mark this as cached + this._cachedCategoryFields = this._categoryFields; + } + // Return the array + return this._categories; + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/methods/_goToFrameIndex.js + this._goToFrameIndex = function (index) { + this._frame = index % this._getCategories().length; + // Draw it with half duration, we want the effect of a 50% animation 50% pause. + this.chart.draw(this.frameDuration / 2); + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/methods/addOrderRule.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.storyboard#wiki-addOrderRule + this.addOrderRule = function (ordering, desc) { + this._orderRules.push({ ordering : ordering, desc : desc }); + }; + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/methods/getFrameValue.js + this.getFrameValue = function () { + var returnValue = null; + if (this._frame >= 0 && this._getCategories().length > this._frame) { + returnValue = this._getCategories()[this._frame]; + } + return returnValue; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/methods/goToFrame.js + this.goToFrame = function (frameText) { + if (this._getCategories().length > 0) { + var index = this._getCategories().indexOf(frameText); + this._goToFrameIndex(index); + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/methods/pauseAnimation.js + this.pauseAnimation = function () { + if (this._animationTimer !== null) { + window.clearInterval(this._animationTimer); + this._animationTimer = null; + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/methods/startAnimation.js + this.startAnimation = function () { + if (this._animationTimer === null) { + if (this.onTick !== null) { this.onTick(this.getFrameValue()); } + this._animationTimer = window.setInterval((function (storyboard) { + return function () { + storyboard._goToFrameIndex(storyboard._frame + 1); + if (storyboard.onTick !== null) { + storyboard.onTick(storyboard.getFrameValue()); + } + storyboard._drawText(storyboard.frameDuration / 2); + }; + }(this)), this.frameDuration); + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/storyboard/methods/stopAnimation.js + this.stopAnimation = function () { + if (this._animationTimer !== null) { + window.clearInterval(this._animationTimer); + this._animationTimer = null; + this._frame = 0; + } + }; + + + }; + // End dimple.storyboard + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/aggregateMethod/avg.js + dimple.aggregateMethod.avg = function (lhs, rhs) { + lhs.value = (lhs.value === null || lhs.value === undefined ? 0 : parseFloat(lhs.value)); + lhs.count = (lhs.count === null || lhs.count === undefined ? 1 : parseFloat(lhs.count)); + rhs.value = (rhs.value === null || rhs.value === undefined ? 0 : parseFloat(rhs.value)); + rhs.count = (rhs.count === null || rhs.count === undefined ? 1 : parseFloat(rhs.count)); + return ((lhs.value * lhs.count) + (rhs.value * rhs.count)) / (lhs.count + rhs.count); + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/aggregateMethod/count.js + dimple.aggregateMethod.count = function (lhs, rhs) { + lhs.count = (lhs.count === null || lhs.count === undefined ? 0 : parseFloat(lhs.count)); + rhs.count = (rhs.count === null || rhs.count === undefined ? 0 : parseFloat(rhs.count)); + return lhs.count + rhs.count; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/aggregateMethod/max.js + dimple.aggregateMethod.max = function (lhs, rhs) { + lhs.value = (lhs.value === null || lhs.value === undefined ? 0 : parseFloat(lhs.value)); + rhs.value = (rhs.value === null || rhs.value === undefined ? 0 : parseFloat(rhs.value)); + return lhs.value > rhs.value ? lhs.value : rhs.value; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/aggregateMethod/min.js + dimple.aggregateMethod.min = function (lhs, rhs) { + return (lhs.value === null ? parseFloat(rhs.value) : (parseFloat(lhs.value) < parseFloat(rhs.value) ? parseFloat(lhs.value) : parseFloat(rhs.value))); + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/aggregateMethod/sum.js + dimple.aggregateMethod.sum = function (lhs, rhs) { + lhs.value = (lhs.value === null || lhs.value === undefined ? 0 : parseFloat(lhs.value)); + rhs.value = (rhs.value === null || rhs.value === undefined ? 0 : parseFloat(rhs.value)); + return lhs.value + rhs.value; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/plot/area.js + dimple.plot.area = { + stacked: true, + + supportedAxes: ["x", "y", "c"], + + draw: function (chart, series, duration) { + // Get self pointer for inner functions + var self = this, + data = series._positionData, + uniqueValues = [], + firstAgg = 1, + graded = false, + line, + catPoints = {}, + markers; + + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + + // If there is a category axis we should draw a line for each aggField. Otherwise + // the first aggField defines the points and the others define the line + if (series.x._hasCategories() || series.y._hasCategories()) { + firstAgg = 0; + } + data.forEach(function (d) { + var filter = [], + match = false, + k; + for (k = firstAgg; k < d.aggField.length; k += 1) { + filter.push(d.aggField[k]); + } + uniqueValues.forEach(function (e) { + match = match || (e === filter.join("/")); + }, this); + if (!match) { + uniqueValues.push(filter.join("/")); + } + }, this); + + if (series.c !== null && series.c !== undefined && ((series.x._hasCategories() && series.y._hasMeasure()) || (series.y._hasCategories() && series.x._hasMeasure()))) { + graded = true; + uniqueValues.forEach(function (seriesValue) { + dimple._addGradient(seriesValue, "fill-area-gradient-" + seriesValue.join("_").replace(" ", ""), (series.x._hasCategories() ? series.x : series.y), data, chart, duration, "fill"); + dimple._addGradient(seriesValue, "stroke-area-gradient-" + seriesValue.join("_").replace(" ", ""), (series.x._hasCategories() ? series.x : series.y), data, chart, duration, "stroke"); + }, this); + } + + line = d3.svg.line() + .x(function (d) { return dimple._helpers.cx(d, chart, series); }) + .y(function (d) { return dimple._helpers.cy(d, chart, series); }); + + if (series.shapes === null || series.shapes === undefined) { + series.shapes = chart._group.selectAll(".area") + .data(uniqueValues) + .enter() + .append("svg:path") + .attr("opacity", function(d) { return chart.getColor(d).opacity; }); + } + + series.shapes + .data(uniqueValues) + .transition() + .duration(duration) + .attr("class", function (d) { return "series area " + d.replace(" ", ""); }) + .attr("d", function (d) { + var seriesData, + baseline = [], + max = 0, + row, + newObj, + j, + k, + m, + q, + r; + seriesData = dimple.filterData(data, "aggField", d); + seriesData.sort(function (a, b) { + var sortValue = 0; + if (series.x._hasCategories()) { + sortValue = (dimple._helpers.cx(a, chart, series) < dimple._helpers.cx(b, chart, series) ? -1 : 1); + } else if (series.y._hasCategories()) { + sortValue = (dimple._helpers.cy(a, chart, series) < dimple._helpers.cy(b, chart, series) ? -1 : 1); + } + return sortValue; + }); + for (j = seriesData.length - 1; j >= 0; j -= 1) { + row = seriesData[j]; + newObj = { cx: 0, cy: 0, height: 0, width: 0, xOffset: 0, yOffset: 0 }; + if (series.x._hasCategories()) { + // Fix the x properties + newObj.cx = row.cx; + newObj.width = row.width; + newObj.xOffset = row.xOffset; + // Find the largest value for the xField less than this value + if (catPoints[row.xField] === undefined) { + catPoints[row.xField] = []; + } else { + max = 0; + for (k = 0; k <= catPoints[row.xField].length; k += 1) { + q = catPoints[row.xField][k]; + if ((row.cy >= 0 && q >= 0) || (row.cy <= 0 && q <= 0)) { + if (Math.abs(q) <= Math.abs(row.cy) && Math.abs(q) > Math.abs(max)) { + max = q; + } + } + } + newObj.cy = max; + } + baseline.push(newObj); + catPoints[row.xField].push(row.cy); + } else if (series.y._hasCategories()) { + // Fix the y properties + newObj.cy = row.cy; + newObj.height = row.height; + newObj.yOffset = row.yOffset; + // Find the largest value for the xField less than this value + if (catPoints[row.yField] === undefined) { + catPoints[row.yField] = []; + } else { + max = 0; + for (m = 0; m <= catPoints[row.yField].length; m += 1) { + r = catPoints[row.yField][m]; + if ((row.cx >= 0 && r >= 0) || (row.cx <= 0 && r <= 0)) { + if (Math.abs(r) <= Math.abs(row.cx) && Math.abs(r) > Math.abs(max)) { + max = r; + } + } + } + newObj.cx = max; + } + baseline.push(newObj); + catPoints[row.yField].push(row.cx); + } + } + //return line(startPoint.concat(seriesData).concat(endPoint)); + return line(seriesData.concat(baseline).concat(seriesData[0])); + }) + .call(function () { + if (!chart.noFormats) { + this.attr("fill", function (d) { return (graded ? "url(#fill-area-gradient-" + d.join("_").replace(" ", "") + ")" : chart.getColor(d).fill); }) + .attr("stroke", function (d) { return (graded ? "url(#stroke-area-gradient-" + d.join("_").replace(" ", "") + ")" : chart.getColor(d).stroke); }) + .attr("stroke-width", series.lineWeight); + } + }); + + // Add line markers. + markers = chart._group.selectAll(".markers") + .data(data) + .enter(); + + // Add a fully opaque white circle first so we don't see a ghost of the line + if (series.lineMarkers) { + markers.append("circle") + .transition().duration(duration) + .attr("cx", function (d) { return dimple._helpers.cx(d, chart, series); }) + .attr("cy", function (d) { return dimple._helpers.cy(d, chart, series); }) + .attr("r", 2 + series.lineWeight) + .attr("fill", "white") + .attr("stroke", "none"); + } + + // Add the actual marker. We need to do this even if we aren't displaying them because they + // catch hover events + markers.append("circle") + .on("mouseover", function (e) { + self.enterEventHandler(e, this, chart, series); + }) + .on("mouseleave", function (e) { + self.leaveEventHandler(e, this, chart, series); + }) + .transition().duration(duration) + .attr("cx", function (d) { return dimple._helpers.cx(d, chart, series); }) + .attr("cy", function (d) { return dimple._helpers.cy(d, chart, series); }) + .attr("r", 2 + series.lineWeight) + .attr("opacity", function (d) { return (series.lineMarkers ? chart.getColor(d).opacity : 0); }) + .call(function () { + if (!chart.noFormats) { + this.attr("fill", "white") + .style("stroke-width", series.lineWeight) + .attr("stroke", function (d) { return dimple._helpers.stroke(d, chart, series); }); + } + }); + }, + + // Handle the mouse enter event + enterEventHandler: function (e, shape, chart, series) { + + // The margin between the text and the box + var textMargin = 5, + // The margin between the ring and the popup + popupMargin = 10, + // The popup animation duration in ms + animDuration = 750, + // Collect some facts about the highlighted bubble + selectedShape = d3.select(shape), + cx = parseFloat(selectedShape.attr("cx")), + cy = parseFloat(selectedShape.attr("cy")), + r = parseFloat(selectedShape.attr("r")), + opacity = dimple._helpers.opacity(e, chart, series), + fill = dimple._helpers.fill(e, chart, series), + dropDest = series._dropLineOrigin(), + // Fade the popup stroke mixing the shape fill with 60% white + popupStrokeColor = d3.rgb( + d3.rgb(fill).r + 0.6 * (255 - d3.rgb(fill).r), + d3.rgb(fill).g + 0.6 * (255 - d3.rgb(fill).g), + d3.rgb(fill).b + 0.6 * (255 - d3.rgb(fill).b) + ), + // Fade the popup fill mixing the shape fill with 80% white + popupFillColor = d3.rgb( + d3.rgb(fill).r + 0.8 * (255 - d3.rgb(fill).r), + d3.rgb(fill).g + 0.8 * (255 - d3.rgb(fill).g), + d3.rgb(fill).b + 0.8 * (255 - d3.rgb(fill).b) + ), + t, + y = 0, + w = 0, + h = 0, + box, + overlap, + rows = []; + + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + chart._tooltipGroup = chart.svg.append("g"); + + // On hover make the line marker visible immediately + selectedShape.style("opacity", 1); + // Add a ring around the data point + chart._tooltipGroup.append("circle") + .attr("cx", cx) + .attr("cy", cy) + .attr("r", r) + .attr("opacity", 0) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 1) + .transition() + .duration(animDuration / 2) + .ease("linear") + .attr("opacity", 1) + .attr("r", r + 4) + .style("stroke-width", 2); + + // Add a drop line to the x axis + if (dropDest.y !== null) { + chart._tooltipGroup.append("line") + .attr("x1", cx) + .attr("y1", (cy < dropDest.y ? cy + r + 4 : cy - r - 4)) + .attr("x2", cx) + .attr("y2", (cy < dropDest.y ? cy + r + 4 : cy - r - 4)) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 2) + .style("stroke-dasharray", ("3, 3")) + .style("opacity", opacity) + .transition() + .delay(animDuration / 2) + .duration(animDuration / 2) + .ease("linear") + // Added 1px offset to cater for svg issue where a transparent + // group overlapping a line can sometimes hide it in some browsers + // Issue #10 + .attr("y2", (cy < dropDest.y ? dropDest.y - 1 : dropDest.y + 1)); + } + + // Add a drop line to the y axis + if (dropDest.x !== null) { + chart._tooltipGroup.append("line") + .attr("x1", (cx < dropDest.x ? cx + r + 4 : cx - r - 4)) + .attr("y1", cy) + .attr("x2", (cx < dropDest.x ? cx + r + 4 : cx - r - 4)) + .attr("y2", cy) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 2) + .style("stroke-dasharray", ("3, 3")) + .style("opacity", opacity) + .transition() + .delay(animDuration / 2) + .duration(animDuration / 2) + .ease("linear") + // Added 1px offset to cater for svg issue where a transparent + // group overlapping a line can sometimes hide it in some browsers + // Issue #10 + .attr("x2", (cx < dropDest.x ? dropDest.x - 1 : dropDest.x + 1)); + } + + // Add a group for text + t = chart._tooltipGroup.append("g"); + // Create a box for the popup in the text group + box = t.append("rect"); + + // Add the series categories + if (series.categoryFields !== null && series.categoryFields !== undefined && series.categoryFields.length > 0) { + series.categoryFields.forEach(function (c, i) { + // If the category name and value match don't display the category name + rows.push(c + (e.aggField[i] !== c ? ": " + e.aggField[i] : "")); + }, this); + } + + if (series.x._hasTimeField()) { + rows.push(series.x.timeField + ": " + series.x._getFormat()(e.xField[0])); + } else if (series.x._hasCategories()) { + // Add the x axis categories + series.x.categoryFields.forEach(function (c, i) { + // If the category name and value match don't display the category name + rows.push(c + (e.xField[i] !== c ? ": " + e.xField[i] : "")); + }, this); + } else { + // Add the axis measure value + rows.push(series.x.measure + ": " + series.x._getFormat()(e.width)); + } + + if (series.y._hasTimeField()) { + rows.push(series.y.timeField + ": " + series.y._getFormat()(e.yField[0])); + } else if (series.y._hasCategories()) { + // Add the y axis categories + series.y.categoryFields.forEach(function (c, i) { + rows.push(c + (e.yField[i] !== c ? ": " + e.yField[i] : "")); + }, this); + } else { + // Add the axis measure value + rows.push(series.y.measure + ": " + series.y._getFormat()(e.height)); + } + + if (series.z !== null && series.z !== undefined) { + // Add the axis measure value + rows.push(series.z.measure + ": " + series.z._getFormat()(e.zValue)); + } + + if (series.c !== null && series.c !== undefined) { + // Add the axis measure value + rows.push(series.c.measure + ": " + series.c._getFormat()(e.cValue)); + } + + // Get distinct text rows to deal with cases where 2 axes have the same dimensionality + rows = rows.filter(function(elem, pos) { + return rows.indexOf(elem) === pos; + }); + + // Create a text object for every row in the popup + t.selectAll(".textHoverShapes").data(rows).enter() + .append("text") + .text(function (d) { return d; }) + .style("font-family", "sans-serif") + .style("font-size", "10px"); + + // Get the max height and width of the text items + t.each(function () { + w = (this.getBBox().width > w ? this.getBBox().width : w); + h = (this.getBBox().width > h ? this.getBBox().height : h); + }); + + // Position the text relatve to the bubble, the absolute positioning + // will be done by translating the group + t.selectAll("text") + .attr("x", 0) + .attr("y", function () { + // Increment the y position + y += this.getBBox().height; + // Position the text at the centre point + return y - (this.getBBox().height / 2); + }); + + // Draw the box with a margin around the text + box.attr("x", -textMargin) + .attr("y", -textMargin) + .attr("height", Math.floor(y + textMargin) - 0.5) + .attr("width", w + 2 * textMargin) + .attr("rx", 5) + .attr("ry", 5) + .style("fill", popupFillColor) + .style("stroke", popupStrokeColor) + .style("stroke-width", 2) + .style("opacity", 0.95); + + // Shift the ring margin left or right depending on whether it will overlap the edge + overlap = cx + r + textMargin + popupMargin + w > parseFloat(chart.svg.attr("width")); + + // Translate the shapes to the x position of the bubble (the x position of the shapes is handled) + t.attr("transform", "translate(" + + (overlap ? cx - (r + textMargin + popupMargin + w) : cx + r + textMargin + popupMargin) + " , " + + (cy - ((y - (h - textMargin)) / 2)) + + ")"); + }, + + // Handle the mouse leave event + leaveEventHandler: function (e, shape, chart, series) { + // Return the opacity of the marker + d3.select(shape).style("opacity", (series.lineMarkers ? dimple._helpers.opacity(e, chart, series) : 0)); + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/plot/bar.js + dimple.plot.bar = { + + // By default the bar series is stacked if there are series categories + stacked: true, + + // The axes which will affect the bar chart - not z + supportedAxes: ["x", "y", "c"], + + // Draw the chart + draw: function (chart, series, duration) { + + // Get self pointer for inner functions + var self = this, + // Get the series data + chartData = series._positionData, + // If the series is uninitialised create placeholders, otherwise use the existing shapes + theseShapes = null, + className = "series" + chart.series.indexOf(series); + + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + + if (series.shapes === null || series.shapes === undefined) { + theseShapes = chart._group.selectAll("." + className).data(chartData); + } else { + theseShapes = series.shapes.data(chartData, function (d) { return d.key; }); + } + + // Add + theseShapes + .enter() + .append("rect") + .attr("id", function (d) { return d.key; }) + .attr("class", function (d) { return className + " bar " + d.aggField.join(" ") + " " + d.xField.join(" ") + " " + d.yField.join(" "); }) + .attr("x", function (d) { return dimple._helpers.x(d, chart, series); }) + .attr("y", function (d) { return dimple._helpers.y(d, chart, series) + dimple._helpers.height(d, chart, series); }) + .attr("width", function (d) {return (d.xField !== null && d.xField.length > 0 ? dimple._helpers.width(d, chart, series) : 0); }) + .attr("height", function (d) {return (d.yField !== null && d.yField.length > 0 ? dimple._helpers.height(d, chart, series) : 0); }) + .attr("opacity", function (d) { return dimple._helpers.opacity(d, chart, series); }) + .on("mouseover", function (e) { + self.enterEventHandler(e, this, chart, series); + }) + .on("mouseleave", function () { + self.leaveEventHandler(chart); + }) + .call(function () { + if (!chart.noFormats) { + this.attr("fill", function (d) { return dimple._helpers.fill(d, chart, series); }) + .attr("stroke", function (d) { return dimple._helpers.stroke(d, chart, series); }); + } + }); + + // Update + theseShapes + .transition().duration(duration) + .attr("x", function (d) { return dimple._helpers.x(d, chart, series); }) + .attr("y", function (d) { return dimple._helpers.y(d, chart, series); }) + .attr("width", function (d) { return dimple._helpers.width(d, chart, series); }) + .attr("height", function (d) { return dimple._helpers.height(d, chart, series); }) + .call(function () { + if (!chart.noFormats) { + this.attr("fill", function (d) { return dimple._helpers.fill(d, chart, series); }) + .attr("stroke", function (d) { return dimple._helpers.stroke(d, chart, series); }); + } + }); + + // Remove + theseShapes + .exit() + .transition().duration(duration) + .attr("x", function (d) { return dimple._helpers.x(d, chart, series); }) + .attr("y", function (d) { return dimple._helpers.y(d, chart, series); }) + .attr("width", function (d) { return dimple._helpers.width(d, chart, series); }) + .attr("height", function (d) { return dimple._helpers.height(d, chart, series); }) + .each("end", function () { + d3.select(this).remove(); + }); + + // Save the shapes to the series array + series.shapes = theseShapes; + }, + + // Handle the mouse enter event + enterEventHandler: function (e, shape, chart, series) { + + // The margin between the text and the box + var textMargin = 5, + // The margin between the ring and the popup + popupMargin = 10, + // The popup animation duration in ms + animDuration = 750, + // Collect some facts about the highlighted bubble + selectedShape = d3.select(shape), + x = parseFloat(selectedShape.attr("x")), + y = parseFloat(selectedShape.attr("y")), + width = parseFloat(selectedShape.attr("width")), + height = parseFloat(selectedShape.attr("height")), + opacity = selectedShape.attr("opacity"), + fill = selectedShape.attr("fill"), + dropDest = series._dropLineOrigin(), + // Fade the popup stroke mixing the shape fill with 60% white + popupStrokeColor = d3.rgb( + d3.rgb(fill).r + 0.6 * (255 - d3.rgb(fill).r), + d3.rgb(fill).g + 0.6 * (255 - d3.rgb(fill).g), + d3.rgb(fill).b + 0.6 * (255 - d3.rgb(fill).b) + ), + // Fade the popup fill mixing the shape fill with 80% white + popupFillColor = d3.rgb( + d3.rgb(fill).r + 0.8 * (255 - d3.rgb(fill).r), + d3.rgb(fill).g + 0.8 * (255 - d3.rgb(fill).g), + d3.rgb(fill).b + 0.8 * (255 - d3.rgb(fill).b) + ), + t, + box, + rows = [], + // The running y value for the text elements + yRunning = 0, + // The maximum bounds of the text elements + w = 0, + h = 0; + + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + chart._tooltipGroup = chart.svg.append("g"); + + // Add a drop line to the x axis + if (!series.x._hasCategories() && dropDest.y !== null) { + chart._tooltipGroup.append("line") + .attr("x1", (x < series.x._origin ? x + 1 : x + width - 1)) + .attr("y1", (y < dropDest.y ? y + height : y)) + .attr("x2", (x < series.x._origin ? x + 1 : x + width - 1)) + .attr("y2", (y < dropDest.y ? y + height : y)) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 2) + .style("stroke-dasharray", ("3, 3")) + .style("opacity", opacity) + .transition() + .delay(animDuration / 2) + .duration(animDuration / 2) + .ease("linear") + // Added 1px offset to cater for svg issue where a transparent + // group overlapping a line can sometimes hide it in some browsers + // Issue #10 + .attr("y2", (y < dropDest.y ? dropDest.y - 1 : dropDest.y + 1)); + } + + // Add a drop line to the y axis + if (!series.y._hasCategories() && dropDest.x !== null) { + chart._tooltipGroup.append("line") + .attr("x1", (x < dropDest.x ? x + width : x)) + .attr("y1", (y < series.y._origin ? y + 1 : y + height - 1)) + .attr("x2", (x < dropDest.x ? x + width : x)) + .attr("y2", (y < series.y._origin ? y + 1 : y + height - 1)) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 2) + .style("stroke-dasharray", ("3, 3")) + .style("opacity", opacity) + .transition() + .delay(animDuration / 2) + .duration(animDuration / 2) + .ease("linear") + // Added 1px offset to cater for svg issue where a transparent + // group overlapping a line can sometimes hide it in some browsers + // Issue #10 + .attr("x2", (x < dropDest.x ? dropDest.x - 1 : dropDest.x + 1)); + } + + // Add a group for text + t = chart._tooltipGroup.append("g"); + // Create a box for the popup in the text group + box = t.append("rect"); + + // Add the series categories + if (series.categoryFields !== null && series.categoryFields !== undefined && series.categoryFields.length > 0) { + series.categoryFields.forEach(function (c, i) { + // If the category name and value match don't display the category name + rows.push(c + (e.aggField[i] !== c ? ": " + e.aggField[i] : "")); + }, this); + } + + if (series.x._hasTimeField()) { + rows.push(series.x.timeField + ": " + series.x._getFormat()(e.xField[0])); + } else if (series.x._hasCategories()) { + // Add the x axis categories + series.x.categoryFields.forEach(function (c, i) { + // If the category name and value match don't display the category name + rows.push(c + (e.xField[i] !== c ? ": " + e.xField[i] : "")); + }, this); + } else { + // Add the axis measure value + rows.push(series.x.measure + ": " + series.x._getFormat()(e.width)); + } + + if (series.y._hasTimeField()) { + rows.push(series.y.timeField + ": " + series.y._getFormat()(e.yField[0])); + } else if (series.y._hasCategories()) { + // Add the y axis categories + series.y.categoryFields.forEach(function (c, i) { + rows.push(c + (e.yField[i] !== c ? ": " + e.yField[i] : "")); + }, this); + } else { + // Add the axis measure value + rows.push(series.y.measure + ": " + series.y._getFormat()(e.height)); + } + + if (series.c !== null && series.c !== undefined) { + // Add the axis measure value + rows.push(series.c.measure + ": " + series.c._getFormat()(series.c.showPercent ? e.cPct : e.cValue)); + } + + // Get distinct text rows to deal with cases where 2 axes have the same dimensionality + rows = rows.filter(function(elem, pos) { + return rows.indexOf(elem) === pos; + }); + + // Create a text object for every row in the popup + t.selectAll(".textHoverShapes").data(rows).enter() + .append("text") + .text(function (d) { return d; }) + .style("font-family", "sans-serif") + .style("font-size", "10px"); + + // Get the max height and width of the text items + t.each(function () { + w = (this.getBBox().width > w ? this.getBBox().width : w); + h = (this.getBBox().width > h ? this.getBBox().height : h); + }); + + // Position the text relatve to the bubble, the absolute positioning + // will be done by translating the group + t.selectAll("text") + .attr("x", 0) + .attr("y", function () { + // Increment the y position + yRunning += this.getBBox().height; + // Position the text at the centre point + return yRunning - (this.getBBox().height / 2); + }); + + // Draw the box with a margin around the text + box.attr("x", -textMargin) + .attr("y", -textMargin) + .attr("height", Math.floor(yRunning + textMargin) - 0.5) + .attr("width", w + 2 * textMargin) + .attr("rx", 5) + .attr("ry", 5) + .style("fill", popupFillColor) + .style("stroke", popupStrokeColor) + .style("stroke-width", 2) + .style("opacity", 0.95); + + // Shift the popup around to avoid overlapping the svg edge + if (x + width + textMargin + popupMargin + w < parseFloat(chart.svg.attr("width"))) { + // Draw centre right + t.attr("transform", "translate(" + + (x + width + textMargin + popupMargin) + " , " + + (y + (height / 2) - ((yRunning - (h - textMargin)) / 2)) + + ")"); + } else if (x - (textMargin + popupMargin + w) > 0) { + // Draw centre left + t.attr("transform", "translate(" + + (x - (textMargin + popupMargin + w)) + " , " + + (y + (height / 2) - ((yRunning - (h - textMargin)) / 2)) + + ")"); + } else if (y + height + yRunning + popupMargin + textMargin < parseFloat(chart.svg.attr("height"))) { + // Draw centre below + t.attr("transform", "translate(" + + (x + (width / 2) - (2 * textMargin + w) / 2) + " , " + + (y + height + 2 * textMargin) + + ")"); + } else { + // Draw centre above + t.attr("transform", "translate(" + + (x + (width / 2) - (2 * textMargin + w) / 2) + " , " + + (y - yRunning - (h - textMargin)) + + ")"); + } + }, + + // Handle the mouse leave event + leaveEventHandler: function (chart) { + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/plot/bubble.js + dimple.plot.bubble = { + + // By default the bubble values are not stacked + stacked: false, + + // The axis positions affecting the bubble series + supportedAxes: ["x", "y", "z", "c"], + + // Draw the axis + draw: function (chart, series, duration) { + + // Get self pointer for inner functions + var self = this, + // Get the series data + chartData = series._positionData, + // If the series is uninitialised create placeholders, otherwise use the existing shapes + theseShapes = null, + className = "series" + chart.series.indexOf(series); + + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + + if (series.shapes === null || series.shapes === undefined) { + theseShapes = chart._group.selectAll("." + className).data(chartData); + } else { + theseShapes = series.shapes.data(chartData, function (d) { return d.key; }); + } + + // Add + theseShapes + .enter() + .append("circle") + .attr("id", function (d) { return d.key; }) + .attr("class", function (d) { return className + " bubble " + d.aggField.join(" ") + " " + d.xField.join(" ") + " " + d.yField.join(" ") + " " + d.zField.join(" "); }) + .attr("cx", function (d) { + return (series.x._hasCategories() ? dimple._helpers.cx(d, chart, series) : series.x._origin); + }) + .attr("cy", function (d) { + return (series.y._hasCategories() ? dimple._helpers.cy(d, chart, series) : series.y._origin); + }) + .attr("r", 0) + .attr("opacity", function (d) { return dimple._helpers.opacity(d, chart, series); }) + .on("mouseover", function (e) { + self.enterEventHandler(e, this, chart, series); + }) + .on("mouseleave", function () { + self.leaveEventHandler(chart); + }) + .call(function () { + if (!chart.noFormats) { + this.attr("fill", function (d) { return dimple._helpers.fill(d, chart, series); }) + .attr("stroke", function (d) { return dimple._helpers.stroke(d, chart, series); }); + } + }); + + // Update + theseShapes + .transition().duration(duration) + .attr("cx", function (d) { return dimple._helpers.cx(d, chart, series); }) + .attr("cy", function (d) { return dimple._helpers.cy(d, chart, series); }) + .attr("r", function (d) { return dimple._helpers.r(d, chart, series); }) + .call(function () { + if (!chart.noFormats) { + this.attr("fill", function (d) { return dimple._helpers.fill(d, chart, series); }) + .attr("stroke", function (d) { return dimple._helpers.stroke(d, chart, series); }); + } + }); + + // Remove + theseShapes + .exit() + .transition().duration(duration) + .attr("r", 0) + .attr("cx", function (d) { + return (series.x._hasCategories() ? dimple._helpers.cx(d, chart, series) : series.x._origin); + }) + .attr("cy", function (d) { + return (series.y._hasCategories() ? dimple._helpers.cy(d, chart, series) : series.y._origin); + }) + .each("end", function () { + d3.select(this).remove(); + }); + + // Save the shapes to the series array + series.shapes = theseShapes; + }, + + // Handle the mouse enter event + enterEventHandler: function (e, shape, chart, series) { + + // The margin between the text and the box + var textMargin = 5, + // The margin between the ring and the popup + popupMargin = 10, + // The popup animation duration in ms + animDuration = 750, + // Collect some facts about the highlighted bubble + selectedShape = d3.select(shape), + cx = parseFloat(selectedShape.attr("cx")), + cy = parseFloat(selectedShape.attr("cy")), + r = parseFloat(selectedShape.attr("r")), + opacity = selectedShape.attr("opacity"), + fill = selectedShape.attr("fill"), + dropDest = series._dropLineOrigin(), + // Fade the popup stroke mixing the shape fill with 60% white + popupStrokeColor = d3.rgb( + d3.rgb(fill).r + 0.6 * (255 - d3.rgb(fill).r), + d3.rgb(fill).g + 0.6 * (255 - d3.rgb(fill).g), + d3.rgb(fill).b + 0.6 * (255 - d3.rgb(fill).b) + ), + // Fade the popup fill mixing the shape fill with 80% white + popupFillColor = d3.rgb( + d3.rgb(fill).r + 0.8 * (255 - d3.rgb(fill).r), + d3.rgb(fill).g + 0.8 * (255 - d3.rgb(fill).g), + d3.rgb(fill).b + 0.8 * (255 - d3.rgb(fill).b) + ), + t, + box, + rows = [], + // The running y value for the text elements + y = 0, + // The maximum bounds of the text elements + w = 0, + h = 0, + overlap; + + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + chart._tooltipGroup = chart.svg.append("g"); + + // Add a ring around the data point + chart._tooltipGroup.append("circle") + .attr("cx", cx) + .attr("cy", cy) + .attr("r", r) + .attr("opacity", 0) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 1) + .transition() + .duration(animDuration / 2) + .ease("linear") + .attr("opacity", 1) + .attr("r", r + 4) + .style("stroke-width", 2); + + // Add a drop line to the x axis + if (dropDest.y !== null) { + chart._tooltipGroup.append("line") + .attr("x1", cx) + .attr("y1", (cy < dropDest.y ? cy + r + 4 : cy - r - 4)) + .attr("x2", cx) + .attr("y2", (cy < dropDest.y ? cy + r + 4 : cy - r - 4)) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 2) + .style("stroke-dasharray", ("3, 3")) + .style("opacity", opacity) + .transition() + .delay(animDuration / 2) + .duration(animDuration / 2) + .ease("linear") + // Added 1px offset to cater for svg issue where a transparent + // group overlapping a line can sometimes hide it in some browsers + // Issue #10 + .attr("y2", (cy < dropDest.y ? dropDest.y - 1 : dropDest.y + 1)); + } + + // Add a drop line to the y axis + if (dropDest.x !== null) { + chart._tooltipGroup.append("line") + .attr("x1", (cx < dropDest.x ? cx + r + 4 : cx - r - 4)) + .attr("y1", cy) + .attr("x2", (cx < dropDest.x ? cx + r + 4 : cx - r - 4)) + .attr("y2", cy) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 2) + .style("stroke-dasharray", ("3, 3")) + .style("opacity", opacity) + .transition() + .delay(animDuration / 2) + .duration(animDuration / 2) + .ease("linear") + // Added 1px offset to cater for svg issue where a transparent + // group overlapping a line can sometimes hide it in some browsers + // Issue #10 + .attr("x2", (cx < dropDest.x ? dropDest.x - 1 : dropDest.x + 1)); + } + + // Add a group for text + t = chart._tooltipGroup.append("g"); + // Create a box for the popup in the text group + box = t.append("rect"); + + // Add the series categories + if (series.categoryFields !== null && series.categoryFields !== undefined && series.categoryFields.length > 0) { + series.categoryFields.forEach(function (c, i) { + // If the category name and value match don't display the category name + rows.push(c + (e.aggField[i] !== c ? ": " + e.aggField[i] : "")); + }, this); + } + + if (series.x._hasTimeField()) { + rows.push(series.x.timeField + ": " + series.x._getFormat()(e.xField[0])); + } else if (series.x._hasCategories()) { + // Add the x axis categories + series.x.categoryFields.forEach(function (c, i) { + // If the category name and value match don't display the category name + rows.push(c + (e.xField[i] !== c ? ": " + e.xField[i] : "")); + }, this); + } else { + // Add the axis measure value + rows.push(series.x.measure + ": " + series.x._getFormat()(e.cx)); + } + + if (series.y._hasTimeField()) { + rows.push(series.y.timeField + ": " + series.y._getFormat()(e.yField[0])); + } else if (series.y._hasCategories()) { + // Add the y axis categories + series.y.categoryFields.forEach(function (c, i) { + rows.push(c + (e.yField[i] !== c ? ": " + e.yField[i] : "")); + }, this); + } else { + // Add the axis measure value + rows.push(series.y.measure + ": " + series.y._getFormat()(e.cy)); + } + + if (series.z !== null && series.z !== undefined) { + // Add the axis measure value + rows.push(series.z.measure + ": " + series.z._getFormat()(e.zValue)); + } + + if (series.c !== null && series.c !== undefined) { + // Add the axis measure value + rows.push(series.c.measure + ": " + series.c._getFormat()(e.cValue)); + } + + // Get distinct text rows to deal with cases where 2 axes have the same dimensionality + rows = rows.filter(function(elem, pos) { + return rows.indexOf(elem) === pos; + }); + + // Create a text object for every row in the popup + t.selectAll(".textHoverShapes").data(rows).enter() + .append("text") + .text(function (d) { return d; }) + .style("font-family", "sans-serif") + .style("font-size", "10px"); + + // Get the max height and width of the text items + t.each(function () { + w = (this.getBBox().width > w ? this.getBBox().width : w); + h = (this.getBBox().width > h ? this.getBBox().height : h); + }); + + // Position the text relatve to the bubble, the absolute positioning + // will be done by translating the group + t.selectAll("text") + .attr("x", 0) + .attr("y", function () { + // Increment the y position + y += this.getBBox().height; + // Position the text at the centre point + return y - (this.getBBox().height / 2); + }); + + // Draw the box with a margin around the text + box.attr("x", -textMargin) + .attr("y", -textMargin) + .attr("height", Math.floor(y + textMargin) - 0.5) + .attr("width", w + 2 * textMargin) + .attr("rx", 5) + .attr("ry", 5) + .style("fill", popupFillColor) + .style("stroke", popupStrokeColor) + .style("stroke-width", 2) + .style("opacity", 0.95); + + // Shift the ring margin left or right depending on whether it will overlap the edge + overlap = cx + r + textMargin + popupMargin + w > parseFloat(chart.svg.attr("width")); + + // Translate the shapes to the x position of the bubble (the x position of the shapes is handled) + t.attr("transform", "translate(" + + (overlap ? cx - (r + textMargin + popupMargin + w) : cx + r + textMargin + popupMargin) + " , " + + (cy - ((y - (h - textMargin)) / 2)) + + ")"); + }, + + // Handle the mouse leave event + leaveEventHandler: function (chart) { + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/plot/line.js + dimple.plot.line = { + stacked: false, + supportedAxes: ["x", "y", "c"], + draw: function (chart, series, duration) { + + // Get self pointer for inner functions + var self = this, + data = series._positionData, + fillIns = [], + uniqueValues = [], + // If there is a category axis we should draw a line for each aggField. Otherwise + // the first aggField defines the points and the others define the line + firstAgg = 1, + graded = false, + line, + markers; + + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + + if (series.x._hasCategories() || series.y._hasCategories()) { + firstAgg = 0; + } + + data.forEach(function (d) { + var filter = [], + match = false, + k; + + for (k = firstAgg; k < d.aggField.length; k += 1) { + filter.push(d.aggField[k]); + } + + uniqueValues.forEach(function (d) { + match = match || (d.join("/") === filter.join("/")); + }, this); + + if (!match) { + uniqueValues.push(filter); + } + + }, this); + + if (series.c !== null && series.c !== undefined && ((series.x._hasCategories() && series.y._hasMeasure()) || (series.y._hasCategories() && series.x._hasMeasure()))) { + graded = true; + uniqueValues.forEach(function (seriesValue) { + dimple._addGradient(seriesValue, "fill-line-gradient-" + seriesValue.join("_").replace(" ", ""), (series.x._hasCategories() ? series.x : series.y), data, chart, duration, "fill"); + }, this); + } + + line = d3.svg.line() + .x(function (d) { return dimple._helpers.cx(d, chart, series); }) + .y(function (d) { return dimple._helpers.cy(d, chart, series); }); + + if (series.shapes === null || series.shapes === undefined) { + series.shapes = chart._group.selectAll(".line") + .data(uniqueValues) + .enter() + .append("svg:path") + .attr("opacity", function(d) { return chart.getColor(d).opacity; }); + } + series.shapes + .data(uniqueValues) + .transition().duration(duration) + .attr("class", function (d) { return "series line " + d.join("_").replace(" ", ""); }) + .attr("d", function (d) { + var seriesData = []; + data.forEach(function (r) { + var add = true, + k; + for (k = firstAgg; k < r.aggField.length; k += 1) { + add = add && (d[k - firstAgg] === r.aggField[k]); + } + if (add) { + seriesData.push(r); + } + }, this); + seriesData.sort(function (a, b) { + var sortValue = 0; + if (series.x._hasCategories()) { + sortValue = (dimple._helpers.cx(a, chart, series) < dimple._helpers.cx(b, chart, series) ? -1 : 1); + } else if (series.y._hasCategories()) { + sortValue = (dimple._helpers.cy(a, chart, series) < dimple._helpers.cy(b, chart, series) ? -1 : 1); + } + return sortValue; + }); + if (seriesData.length === 1) { + fillIns.push({ + cx: dimple._helpers.cx(seriesData[0], chart, series), + cy: dimple._helpers.cy(seriesData[0], chart, series), + opacity: chart.getColor(d[d.length - 1]).opacity, + color: chart.getColor(d[d.length - 1]).stroke + }); + d3.select(this).remove(); + } + return line(seriesData); + }) + .call(function () { + if (!chart.noFormats) { + this.attr("fill", "none") + .attr("stroke", function (d) { return (graded ? "url(#fill-line-gradient-" + d.join("_").replace(" ", "") + ")" : chart.getColor(d[d.length - 1]).stroke); }) + .attr("stroke-width", series.lineWeight); + } + }); + + // Add line markers. + markers = chart._group.selectAll(".markers") + .data(data) + .enter(); + + // Add a fully opaque white circle first so we don't see a ghost of the line + if (series.lineMarkers) { + markers.append("circle") + .transition().duration(duration) + .attr("cx", function (d) { return dimple._helpers.cx(d, chart, series); }) + .attr("cy", function (d) { return dimple._helpers.cy(d, chart, series); }) + .attr("r", 2 + series.lineWeight) + .attr("fill", "white") + .attr("stroke", "none"); + } + + // Add the actual marker. We need to do this even if we aren't displaying them because they + // catch hover events + markers.append("circle") + .on("mouseover", function (e) { + self.enterEventHandler(e, this, chart, series); + }) + .on("mouseleave", function (e) { + self.leaveEventHandler(e, this, chart, series); + }) + .transition().duration(duration) + .attr("cx", function (d) { return dimple._helpers.cx(d, chart, series); }) + .attr("cy", function (d) { return dimple._helpers.cy(d, chart, series); }) + .attr("r", 2 + series.lineWeight) + .attr("opacity", function (d) { return (series.lineMarkers ? chart.getColor(d).opacity : 0); }) + .call(function () { + if (!chart.noFormats) { + this.attr("fill", "white") + .style("stroke-width", series.lineWeight) + .attr("stroke", function (d) { + return (graded ? dimple._helpers.fill(d, chart, series) : chart.getColor(d.aggField[d.aggField.length - 1]).stroke); + }); + } + }); + + // Deal with single point lines if there are no markers + if (!series.lineMarkers) { + chart._group.selectAll(".fill") + .data(fillIns) + .enter() + .append("circle") + .attr("cx", function (d) { return d.cx; }) + .attr("cy", function (d) { return d.cy; }) + .attr("r", series.lineWeight) + .attr("opacity", function (d) { return d.opacity; }) + .call(function () { + if (!chart.noFormats) { + this.attr("fill", function (d) { return d.color; }) + .attr("stroke", "none"); + } + }); + } + }, + + // Handle the mouse enter event + enterEventHandler: function (e, shape, chart, series) { + + // The margin between the text and the box + var textMargin = 5, + // The margin between the ring and the popup + popupMargin = 10, + // The popup animation duration in ms + animDuration = 750, + // Collect some facts about the highlighted bubble + selectedShape = d3.select(shape), + cx = parseFloat(selectedShape.attr("cx")), + cy = parseFloat(selectedShape.attr("cy")), + r = parseFloat(selectedShape.attr("r")), + opacity = dimple._helpers.opacity(e, chart, series), + fill = selectedShape.attr("stroke"), + dropDest = series._dropLineOrigin(), + // Fade the popup stroke mixing the shape fill with 60% white + popupStrokeColor = d3.rgb( + d3.rgb(fill).r + 0.6 * (255 - d3.rgb(fill).r), + d3.rgb(fill).g + 0.6 * (255 - d3.rgb(fill).g), + d3.rgb(fill).b + 0.6 * (255 - d3.rgb(fill).b) + ), + // Fade the popup fill mixing the shape fill with 80% white + popupFillColor = d3.rgb( + d3.rgb(fill).r + 0.8 * (255 - d3.rgb(fill).r), + d3.rgb(fill).g + 0.8 * (255 - d3.rgb(fill).g), + d3.rgb(fill).b + 0.8 * (255 - d3.rgb(fill).b) + ), + // The running y value for the text elements + y = 0, + // The maximum bounds of the text elements + w = 0, + h = 0, + t, + box, + rows = [], + overlap; + + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + chart._tooltipGroup = chart.svg.append("g"); + + // On hover make the line marker visible immediately + selectedShape.style("opacity", 1); + + // Add a ring around the data point + chart._tooltipGroup.append("circle") + .attr("cx", cx) + .attr("cy", cy) + .attr("r", r) + .attr("opacity", 0) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 1) + .transition() + .duration(animDuration / 2) + .ease("linear") + .attr("opacity", 1) + .attr("r", r + series.lineWeight + 2) + .style("stroke-width", 2); + + // Add a drop line to the x axis + if (dropDest.y !== null) { + chart._tooltipGroup.append("line") + .attr("x1", cx) + .attr("y1", (cy < dropDest.y ? cy + r + series.lineWeight + 2 : cy - r - series.lineWeight - 2)) + .attr("x2", cx) + .attr("y2", (cy < dropDest.y ? cy + r + series.lineWeight + 2 : cy - r - series.lineWeight - 2)) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 2) + .style("stroke-dasharray", ("3, 3")) + .style("opacity", opacity) + .transition() + .delay(animDuration / 2) + .duration(animDuration / 2) + .ease("linear") + // Added 1px offset to cater for svg issue where a transparent + // group overlapping a line can sometimes hide it in some browsers + // Issue #10 + .attr("y2", (cy < dropDest.y ? dropDest.y - 1 : dropDest.y + 1)); + } + + // Add a drop line to the y axis + if (dropDest.x !== null) { + chart._tooltipGroup.append("line") + .attr("x1", (cx < dropDest.x ? cx + r + series.lineWeight + 2 : cx - r - series.lineWeight - 2)) + .attr("y1", cy) + .attr("x2", (cx < dropDest.x ? cx + r + series.lineWeight + 2 : cx - r - series.lineWeight - 2)) + .attr("y2", cy) + .style("fill", "none") + .style("stroke", fill) + .style("stroke-width", 2) + .style("stroke-dasharray", ("3, 3")) + .style("opacity", opacity) + .transition() + .delay(animDuration / 2) + .duration(animDuration / 2) + .ease("linear") + // Added 1px offset to cater for svg issue where a transparent + // group overlapping a line can sometimes hide it in some browsers + // Issue #10 + .attr("x2", (cx < dropDest.x ? dropDest.x - 1 : dropDest.x + 1)); + } + + // Add a group for text + t = chart._tooltipGroup.append("g"); + // Create a box for the popup in the text group + box = t.append("rect"); + + // Add the series categories + if (series.categoryFields !== null && series.categoryFields !== undefined && series.categoryFields.length > 0) { + series.categoryFields.forEach(function (c, i) { + // If the category name and value match don't display the category name + rows.push(c + (e.aggField[i] !== c ? ": " + e.aggField[i] : "")); + }, this); + } + + if (series.x._hasTimeField()) { + rows.push(series.x.timeField + ": " + series.x._getFormat()(e.xField[0])); + } else if (series.x._hasCategories()) { + // Add the x axis categories + series.x.categoryFields.forEach(function (c, i) { + // If the category name and value match don't display the category name + rows.push(c + (e.xField[i] !== c ? ": " + e.xField[i] : "")); + }, this); + } else if (series.x.useLog) { + // Add the y axis log + rows.push(series.x.measure + ": " + e.cx); + } else { + // Add the axis measure value + rows.push(series.x.measure + ": " + series.x._getFormat()(e.cx)); + } + + if (series.y._hasTimeField()) { + rows.push(series.y.timeField + ": " + series.y._getFormat()(e.yField[0])); + } else if (series.y._hasCategories()) { + // Add the y axis categories + series.y.categoryFields.forEach(function (c, i) { + rows.push(c + (e.yField[i] !== c ? ": " + e.yField[i] : "")); + }, this); + } else if (series.y.useLog) { + // Add the y axis log + rows.push(series.y.measure + ": " + e.cy); + } else { + // Add the axis measure value + rows.push(series.y.measure + ": " + series.y._getFormat()(e.cy)); + } + + if (series.z !== null && series.z !== undefined) { + // Add the axis measure value + rows.push(series.z.measure + ": " + series.z._getFormat()(e.zValue)); + } + + if (series.c !== null && series.c !== undefined) { + // Add the axis measure value + rows.push(series.c.measure + ": " + series.c._getFormat()(e.cValue)); + } + + // Get distinct text rows to deal with cases where 2 axes have the same dimensionality + rows = rows.filter(function(elem, pos) { + return rows.indexOf(elem) === pos; + }); + + // Create a text object for every row in the popup + t.selectAll(".textHoverShapes").data(rows).enter() + .append("text") + .text(function (d) { return d; }) + .style("font-family", "sans-serif") + .style("font-size", "10px"); + + // Get the max height and width of the text items + t.each(function () { + w = (this.getBBox().width > w ? this.getBBox().width : w); + h = (this.getBBox().width > h ? this.getBBox().height : h); + }); + + // Position the text relatve to the bubble, the absolute positioning + // will be done by translating the group + t.selectAll("text") + .attr("x", 0) + .attr("y", function () { + // Increment the y position + y += this.getBBox().height; + // Position the text at the centre point + return y - (this.getBBox().height / 2); + }); + + // Draw the box with a margin around the text + box.attr("x", -textMargin) + .attr("y", -textMargin) + .attr("height", Math.floor(y + textMargin) - 0.5) + .attr("width", w + 2 * textMargin) + .attr("rx", 5) + .attr("ry", 5) + .style("fill", popupFillColor) + .style("stroke", popupStrokeColor) + .style("stroke-width", 2) + .style("opacity", 0.95); + + // Shift the ring margin left or right depending on whether it will overlap the edge + overlap = cx + r + textMargin + popupMargin + w > parseFloat(chart.svg.attr("width")); + + // Translate the shapes to the x position of the bubble (the x position of the shapes is handled) + t.attr("transform", "translate(" + + (overlap ? cx - (r + textMargin + popupMargin + w) : cx + r + textMargin + popupMargin) + " , " + + (cy - ((y - (h - textMargin)) / 2)) + + ")"); + }, + + // Handle the mouse leave event + leaveEventHandler: function (e, shape, chart, series) { + // Return the opacity of the marker + d3.select(shape).style("opacity", (series.lineMarkers ? dimple._helpers.opacity(e, chart, series) : 0)); + if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { + chart._tooltipGroup.remove(); + } + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/_addGradient.js + dimple._addGradient = function (seriesValue, id, categoryAxis, data, chart, duration, colorProperty) { + + var grad = chart._group.select("#" + id), + cats = [], + field = categoryAxis.position + "Field", + transition = true, + colors = []; + + data.forEach(function (d) { + if (cats.indexOf(d[field]) === -1) { + cats.push(d[field]); + } + }, this); + + cats = cats.sort(function (a, b) { return categoryAxis._scale(a) - categoryAxis._scale(b); }); + + if (grad.node() === null) { + transition = false; + grad = chart._group.append("linearGradient") + .attr("id", id) + .attr("gradientUnits", "userSpaceOnUse") + .attr("x1", (categoryAxis.position === "x" ? categoryAxis._scale(cats[0]) + ((chart._widthPixels() / cats.length) / 2) : 0)) + .attr("y1", (categoryAxis.position === "y" ? categoryAxis._scale(cats[0]) - ((chart._heightPixels() / cats.length) / 2) : 0)) + .attr("x2", (categoryAxis.position === "x" ? categoryAxis._scale(cats[cats.length - 1]) + ((chart._widthPixels() / cats.length) / 2) : 0)) + .attr("y2", (categoryAxis.position === "y" ? categoryAxis._scale(cats[cats.length - 1]) - ((chart._heightPixels() / cats.length) / 2) : 0)); + } + + cats.forEach(function (cat, j) { + + var row = {}, + k = 0; + + for (k = 0; k < data.length; k = k + 1) { + if (data[k].aggField.join("_") === seriesValue.join("_") && data[k][field].join("_") === cat.join("_")) { + row = data[k]; + break; + } + } + + colors.push({ offset: Math.round((j / (cats.length - 1)) * 100) + "%", color: row[colorProperty] }); + }, this); + + if (transition) { + grad.selectAll("stop") + .data(colors) + .transition().duration(duration) + .attr("offset", function(d) { return d.offset; }) + .attr("stop-color", function(d) { return d.color; }); + } else { + grad.selectAll("stop") + .data(colors) + .enter() + .append("stop") + .attr("offset", function(d) { return d.offset; }) + .attr("stop-color", function(d) { return d.color; }); + } + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/objects/chart/methods/_getOrderedList.js + dimple._getOrderedList = function (data, mainField, levelDefinitions) { + var rollupData = [], + sortStack = [], + finalArray = [], + fields = [mainField], + defs = []; + // Force the level definitions into an array + if (levelDefinitions !== null && levelDefinitions !== undefined) { + defs = defs.concat(levelDefinitions); + } + // Add the base case + defs = defs.concat({ ordering: mainField, desc: false }); + // Exclude fields if this does not contain a function + defs.forEach(function (def) { + var field; + if (typeof def.ordering === "function") { + for (field in data[0]) { + if (data[0].hasOwnProperty(field) && fields.indexOf(field) === -1) { + fields.push(field); + } + } + } else if (!(def.ordering instanceof Array)) { + fields.push(def.ordering); + } + }, this); + rollupData = dimple._rollUp(data, mainField, fields); + // If we go below the leaf stop recursing + if (defs.length >= 1) { + // Build a stack of compare methods + // Iterate each level definition + defs.forEach(function (def) { + // Draw ascending by default + var desc = (def.desc === null || def.desc === undefined ? false : def.desc), + ordering = def.ordering, + orderArray = [], + field = (typeof ordering === "string" ? ordering : null), + sum = function (array) { + var total = 0, + i; + for (i = 0; i < array.length; i += 1) { + if (isNaN(array[i])) { + total = 0; + break; + } else { + total += parseFloat(array[i]); + } + } + return total; + }, + compare = function (a, b) { + var result = 0, + sumA = sum(a), + sumB = sum(b); + if (!isNaN(sumA) && sumA !== 0 && !isNaN(sumB) && sumB !== 0) { + result = parseFloat(sumA) - parseFloat(sumB); + } else if (!isNaN(Date.parse(a[0])) && !isNaN(Date.parse(b[0]))) { + result = Date.parse(a[0]) - Date.parse(b[0]); + } else if (a[0] < b[0]) { + result = -1; + } else if (a[0] > b[0]) { + result = 1; + } + return result; + }; + // Handle the ordering based on the type set + if (typeof ordering === "function") { + // Apply the sort predicate directly + sortStack.push(function (a, b) { + return (desc ? -1 : 1) * ordering(a, b); + }); + } else if (ordering instanceof Array) { + // The order list may be an array of arrays + // combine the values with pipe delimiters. + // The delimiter is irrelevant as long as it is consistent + // with the sort predicate below + ordering.forEach(function (d) { + orderArray.push(([].concat(d)).join("|")); + }, this); + // Sort according to the axis position + sortStack.push(function (a, b) { + var aStr = "".concat(a[mainField]), + bStr = "".concat(b[mainField]), + aIx, + bIx; + // If the value is not found it should go to the end (if descending it + // should go to the start so that it ends up at the back when reversed) + aIx = orderArray.indexOf(aStr); + bIx = orderArray.indexOf(bStr); + aIx = (aIx < 0 ? (desc ? -1 : orderArray.length) : aIx); + bIx = (bIx < 0 ? (desc ? -1 : orderArray.length) : bIx); + return (desc ? -1 : 1) * (aIx - bIx); + }); + } else { + // Sort the data + sortStack.push(function (a, b) { + // The result value + var result = 0; + // Find the field + if (a[field] !== undefined && b[field] !== undefined) { + // Compare just the first mapped value + result = compare([].concat(a[field]), [].concat(b[field])); + } + return (desc ? -1 : 1) * result; + }); + } + }); + rollupData.sort(function (a, b) { + var compareIx = 0, + result = 0; + while (compareIx < sortStack.length && result === 0) { + result = sortStack[compareIx](a, b); + compareIx += 1; + } + return result; + }); + // Return a simple array if only one field is being returned. + // for multiple fields remove extra fields but leave objects + rollupData.forEach(function (d) { + finalArray.push(d[mainField]); + }, this); + } + // Return the ordered list + return finalArray; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/_helpers.js + dimple._helpers = { + + // Calculate the centre x position + cx: function (d, chart, series) { + var returnCx = 0; + if (series.x.measure !== null && series.x.measure !== undefined) { + returnCx = series.x._scale(d.cx); + } else if (series.x._hasCategories() && series.x.categoryFields.length >= 2) { + returnCx = series.x._scale(d.cx) + dimple._helpers.xGap(chart, series) + ((d.xOffset + 0.5) * (((chart._widthPixels() / series.x._max) - 2 * dimple._helpers.xGap(chart, series)) * d.width)); + } else { + returnCx = series.x._scale(d.cx) + ((chart._widthPixels() / series.x._max) / 2); + } + return returnCx; + }, + + // Calculate the centre y position + cy: function (d, chart, series) { + var returnCy = 0; + if (series.y.measure !== null && series.y.measure !== undefined) { + returnCy = series.y._scale(d.cy); + } else if (series.y.categoryFields !== null && series.y.categoryFields !== undefined && series.y.categoryFields.length >= 2) { + returnCy = (series.y._scale(d.cy) - (chart._heightPixels() / series.y._max)) + dimple._helpers.yGap(chart, series) + ((d.yOffset + 0.5) * (((chart._heightPixels() / series.y._max) - 2 * dimple._helpers.yGap(chart, series)) * d.height)); + } else { + returnCy = series.y._scale(d.cy) - ((chart._heightPixels() / series.y._max) / 2); + } + return returnCy; + }, + + // Calculate the radius + r: function (d, chart, series) { + var returnR = 0; + if (series.z === null || series.z === undefined) { + returnR = 5; + } else if (series.z._hasMeasure()) { + returnR = series.z._scale(d.r); + } else { + returnR = series.z._scale(chart._heightPixels() / 100); + } + return returnR; + }, + + // Calculate the x gap for bar type charts + xGap: function (chart, series) { + var returnXGap = 0; + if ((series.x.measure === null || series.x.measure === undefined) && series.barGap > 0) { + returnXGap = ((chart._widthPixels() / series.x._max) * (series.barGap > 0.99 ? 0.99 : series.barGap)) / 2; + } + return returnXGap; + }, + + // Calculate the x gap for clusters within bar type charts + xClusterGap: function (d, chart, series) { + var returnXClusterGap = 0; + if (series.x.categoryFields !== null && series.x.categoryFields !== undefined && series.x.categoryFields.length >= 2 && series.clusterBarGap > 0 && !series.x._hasMeasure()) { + returnXClusterGap = (d.width * ((chart._widthPixels() / series.x._max) - (dimple._helpers.xGap(chart, series) * 2)) * (series.clusterBarGap > 0.99 ? 0.99 : series.clusterBarGap)) / 2; + } + return returnXClusterGap; + }, + + // Calculate the y gap for bar type charts + yGap: function (chart, series) { + var returnYGap = 0; + if ((series.y.measure === null || series.y.measure === undefined) && series.barGap > 0) { + returnYGap = ((chart._heightPixels() / series.y._max) * (series.barGap > 0.99 ? 0.99 : series.barGap)) / 2; + } + return returnYGap; + }, + + // Calculate the y gap for clusters within bar type charts + yClusterGap: function (d, chart, series) { + var returnYClusterGap = 0; + if (series.y.categoryFields !== null && series.y.categoryFields !== undefined && series.y.categoryFields.length >= 2 && series.clusterBarGap > 0 && !series.y._hasMeasure()) { + returnYClusterGap = (d.height * ((chart._heightPixels() / series.y._max) - (dimple._helpers.yGap(chart, series) * 2)) * (series.clusterBarGap > 0.99 ? 0.99 : series.clusterBarGap)) / 2; + } + return returnYClusterGap; + }, + + // Calculate the top left x position for bar type charts + x: function (d, chart, series) { + var returnX = 0; + if (series.x._hasTimeField()) { + returnX = series.x._scale(d.x) - (dimple._helpers.width(d, chart, series) / 2); + } else if (series.x.measure !== null && series.x.measure !== undefined) { + returnX = series.x._scale(d.x); + } else { + returnX = series.x._scale(d.x) + dimple._helpers.xGap(chart, series) + (d.xOffset * (dimple._helpers.width(d, chart, series) + 2 * dimple._helpers.xClusterGap(d, chart, series))) + dimple._helpers.xClusterGap(d, chart, series); + } + return returnX; + }, + + // Calculate the top left y position for bar type charts + y: function (d, chart, series) { + var returnY = 0; + if (series.y._hasTimeField()) { + returnY = series.y._scale(d.y) - (dimple._helpers.height(d, chart, series) / 2); + } else if (series.y.measure !== null && series.y.measure !== undefined) { + returnY = series.y._scale(d.y); + } else { + returnY = (series.y._scale(d.y) - (chart._heightPixels() / series.y._max)) + dimple._helpers.yGap(chart, series) + (d.yOffset * (dimple._helpers.height(d, chart, series) + 2 * dimple._helpers.yClusterGap(d, chart, series))) + dimple._helpers.yClusterGap(d, chart, series); + } + return returnY; + }, + + // Calculate the width for bar type charts + width: function (d, chart, series) { + var returnWidth = 0; + if (series.x.measure !== null && series.x.measure !== undefined) { + returnWidth = Math.abs(series.x._scale((d.x < 0 ? d.x - d.width : d.x + d.width)) - series.x._scale(d.x)); + } else if (series.x._hasTimeField()) { + returnWidth = series.x.floatingBarWidth; + } else { + returnWidth = d.width * ((chart._widthPixels() / series.x._max) - (dimple._helpers.xGap(chart, series) * 2)) - (dimple._helpers.xClusterGap(d, chart, series) * 2); + } + return returnWidth; + }, + + // Calculate the height for bar type charts + height: function (d, chart, series) { + var returnHeight = 0; + if (series.y._hasTimeField()) { + returnHeight = series.y.floatingBarWidth; + } else if (series.y.measure !== null && series.y.measure !== undefined) { + returnHeight = Math.abs(series.y._scale(d.y) - series.y._scale((d.y <= 0 ? d.y + d.height : d.y - d.height))); + } else { + returnHeight = d.height * ((chart._heightPixels() / series.y._max) - (dimple._helpers.yGap(chart, series) * 2)) - (dimple._helpers.yClusterGap(d, chart, series) * 2); + } + return returnHeight; + }, + + // Calculate the opacity for series + opacity: function (d, chart, series) { + var returnOpacity = 0; + if (series.c !== null && series.c !== undefined) { + returnOpacity = d.opacity; + } else { + returnOpacity = chart.getColor(d.aggField.slice(-1)[0]).opacity; + } + return returnOpacity; + }, + + // Calculate the fill coloring for series + fill: function (d, chart, series) { + var returnFill = 0; + if (series.c !== null && series.c !== undefined) { + returnFill = d.fill; + } else { + returnFill = chart.getColor(d.aggField.slice(-1)[0]).fill; + } + return returnFill; + }, + + // Calculate the stroke coloring for series + stroke: function (d, chart, series) { + var stroke = 0; + if (series.c !== null && series.c !== undefined) { + stroke = d.stroke; + } else { + stroke = chart.getColor(d.aggField.slice(-1)[0]).stroke; + } + return stroke; + } + + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/_parentHeight.js + dimple._parentHeight = function (parent) { + // This one seems to work in Chrome - good old Chrome! + var returnValue = parent.offsetHeight; + // This does it for IE + if (returnValue <= 0 || returnValue === null || returnValue === undefined) { + returnValue = parent.clientHeight; + } + // FireFox is the hard one this time. See this bug report: + // https://bugzilla.mozilla.org/show_bug.cgi?id=649285// + // It's dealt with by trying to recurse up the dom until we find something + // we can get a size for. Usually the parent of the SVG. It's a bit costly + // but I don't know of any other way. + if (returnValue <= 0 || returnValue === null || returnValue === undefined) { + if (parent.parentNode === null || parent.parentNode === undefined) { + // Give up - Recursion Exit Point + returnValue = 0; + } else { + // Get the size from the parent recursively + returnValue = dimple._parseYPosition(d3.select(parent).attr("height"), parent.parentNode); + } + } + return returnValue; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/_parentWidth.js + dimple._parentWidth = function (parent) { + // This one seems to work in Chrome - good old Chrome! + var returnValue = parent.offsetWidth; + // This does it for IE + if (returnValue <= 0 || returnValue === null || returnValue === undefined) { + returnValue = parent.clientWidth; + } + // FireFox is the hard one this time. See this bug report: + // https://bugzilla.mozilla.org/show_bug.cgi?id=649285// + // It's dealt with by trying to recurse up the dom until we find something + // we can get a size for. Usually the parent of the SVG. It's a bit costly + // but I don't know of any other way. + if (returnValue <= 0 || returnValue === null || returnValue === undefined) { + if (parent.parentNode === null || parent.parentNode === undefined) { + // Give up - Recursion Exit Point + returnValue = 0; + } else { + // Get the size from the parent recursively + returnValue = dimple._parseXPosition(d3.select(parent).attr("width"), parent.parentNode); + } + } + return returnValue; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/_parseXPosition.js + dimple._parseXPosition = function (value, parent) { + var returnValue = 0, + values = value.toString().split(","); + values.forEach(function (v) { + if (v !== undefined && v !== null) { + if (!isNaN(v)) { + returnValue += parseFloat(v); + } else if (v.slice(-1) === "%") { + returnValue += dimple._parentWidth(parent) * (parseFloat(v.slice(0, v.length - 1)) / 100); + } else if (v.slice(-2) === "px") { + returnValue += parseFloat(v.slice(0, v.length - 2)); + } else { + returnValue = value; + } + } + }, this); + return returnValue; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/_parseYPosition.js + dimple._parseYPosition = function (value, parent) { + var returnValue = 0, + values = value.toString().split(","); + values.forEach(function (v) { + if (v !== undefined && v !== null) { + if (!isNaN(v)) { + returnValue += parseFloat(v); + } else if (v.slice(-1) === "%") { + returnValue += dimple._parentHeight(parent) * (parseFloat(v.slice(0, v.length - 1)) / 100); + } else if (v.slice(-2) === "px") { + returnValue += parseFloat(v.slice(0, v.length - 2)); + } else { + returnValue = value; + } + } + }, this); + return returnValue; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/_rollUp.js + dimple._rollUp = function (data, fields, includeFields) { + + var returnList = []; + // Put single values into single value arrays + if (fields !== null && fields !== undefined) { + fields = [].concat(fields); + } else { + fields = []; + } + // Iterate every row in the data + data.forEach(function (d) { + // The index of the corresponding row in the return list + var index = -1, + newRow = {}, + match = true; + // Find the corresponding value in the return list + returnList.forEach(function (r, j) { + if (index === -1) { + // Indicates a match + match = true; + // Iterate the passed fields and compare + fields.forEach(function (f) { + match = match && d[f] === r[f]; + }, this); + // If this is a match get the index + if (match) { + index = j; + } + } + }, this); + // Pick up the matched row, or add a new one + if (index !== -1) { + newRow = returnList[index]; + } else { + // Iterate the passed fields and add to the new row + fields.forEach(function (f) { + newRow[f] = d[f]; + }, this); + returnList.push(newRow); + index = returnList.length - 1; + } + // Iterate all the fields in the data + includeFields.forEach(function (field) { + if (fields.indexOf(field) === -1) { + if (newRow[field] === undefined) { + newRow[field] = []; + } + newRow[field] = newRow[field].concat(d[field]); + } + }, this); + // Update the return list + returnList[index] = newRow; + }, this); + // Return the flattened list + return returnList; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/filterData.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple#wiki-filterData + dimple.filterData = function (data, field, filterValues) { + var returnData = data; + if (field !== null && filterValues !== null) { + // Build an array from a single filter value or use the array + if (filterValues !== null && filterValues !== undefined) { filterValues = [].concat(filterValues); } + // The data to return + returnData = []; + // Iterate all the data + data.forEach(function (d) { + // If an invalid field is passed, just pass the data + if (d[field] === null) { + returnData.push(d); + } else { + if (filterValues.indexOf([].concat(d[field]).join("/")) > -1) { + returnData.push(d); + } + } + }, this); + } + // Return the filtered data + return returnData; + }; + + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/getUniqueValues.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple#wiki-getUniqueValues + dimple.getUniqueValues = function (data, fields) { + var returnlist = []; + // Put single values into single value arrays + if (fields !== null && fields !== undefined) { + fields = [].concat(fields); + // Iterate every row in the data + data.forEach(function (d) { + // Handle multiple category values by iterating the fields in the row and concatonate the values + var field = ""; + fields.forEach(function (f, i) { + if (i > 0) { + field += "/"; + } + field += d[f]; + }, this); + // If the field was not found, add it to the end of the categories array + if (returnlist.indexOf(field) === -1) { + returnlist.push(field); + } + }, this); + } + return returnlist; + }; + + // Copyright: 2013 PMSI-AlignAlytics + // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" + // Source: /src/methods/newSvg.js + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple#wiki-newSvg + dimple.newSvg = function (parentSelector, width, height) { + var selectedShape = null; + if (parentSelector === null || parentSelector === undefined) { + parentSelector = "body"; + } + selectedShape = d3.select(parentSelector); + if (selectedShape.empty()) { + throw "The '" + parentSelector + "' selector did not match any elements. Please prefix with '#' to select by id or '.' to select by class"; + } + return selectedShape.append("svg").attr("width", width).attr("height", height); + }; + + +}()); +// End dimple \ No newline at end of file diff --git a/dist/dimple.v1.1.4.min.js b/dist/dimple.v1.1.4.min.js new file mode 100644 index 0000000..8ce0e10 --- /dev/null +++ b/dist/dimple.v1.1.4.min.js @@ -0,0 +1,2 @@ +var dimple={version:"1.1.4",plot:{},aggregateMethod:{}};!function(){"use strict";dimple.axis=function(a,b,c,d,e){this.chart=a,this.position=b,this.categoryFields=null===e||void 0===e?c:[].concat(e),this.measure=d,this.timeField=e,this.floatingBarWidth=5,this.hidden=!1,this.showPercent=!1,this.colors=null,this.overrideMin=null,this.overrideMax=null,this.shapes=null,this.showGridlines=null,this.gridlineShapes=null,this.titleShape=null,this.dateParseFormat=null,this.tickFormat=null,this.timePeriod=null,this.timeInterval=1,this.useLog=!1,this.logBase=10,this._scale=null,this._min=0,this._max=0,this._previousOrigin=null,this._origin=null,this._orderRules=[],this._groupOrderRules=[],this._draw=null,this._getFormat=function(){var a,b,c,d,e,f,g;return null!==this.tickFormat&&void 0!==this.tickFormat?a=this._hasTimeField()?d3.time.format(this.tickFormat):d3.format(this.tickFormat):this.showPercent?a=d3.format("%"):this.useLog&&null!==this.measure?a=function(a){var b=Math.floor(Math.abs(a),0).toString().length,c=Math.min(Math.floor((b-1)/3),4),d="kmBT".substring(c-1,c),e="0"===Math.round(10*(a/Math.pow(1e3,c))).toString().slice(-1)?0:1;return 0===a?0:d3.format(",."+e+"f")(a/Math.pow(1e3,c))+d}:null!==this.measure?(b=Math.floor(Math.abs(this._max),0).toString(),c=Math.floor(Math.abs(this._min),0).toString(),d=Math.max(c.length,b.length),d>3?(e=Math.min(Math.floor((d-1)/3),4),f="kmBT".substring(e-1,e),g=1>=d-3*e?1:0,a=function(a){return 0===a?0:d3.format(",."+g+"f")(a/Math.pow(1e3,e))+f}):(g=1>=d?1:0,a=d3.format(",."+g+"f"))):a=function(a){return a},a},this._getTimePeriod=function(){var a=this.timePeriod,b=30,c=this._max-this._min;return!this._hasTimeField||null!==this.timePeriod&&void 0!==this.timePeriod||(a=b>=c/1e3?d3.time.seconds:b>=c/6e4?d3.time.minutes:b>=c/36e5?d3.time.hours:b>=c/864e5?d3.time.days:b>=c/6048e5?d3.time.weeks:b>=c/26298e5?d3.time.months:d3.time.years),a},this._hasCategories=function(){return null!==this.categoryFields&&void 0!==this.categoryFields&&this.categoryFields.length>0},this._hasMeasure=function(){return null!==this.measure&&void 0!==this.measure},this._hasTimeField=function(){return null!==this.timeField&&void 0!==this.timeField},this._parseDate=function(a){var b;return b=null===this.dateParseFormat||void 0===this.dateParseFormat?isNaN(a)?Date.parse(a):new Date(a):d3.time.format(this.dateParseFormat).parse(a)},this._update=function(a){var b,c,d,e,f=[],g=function(a,b,c){var d,e=a.categoryFields[0],f=e,g=!1,h=!0,i=[];for(d=0;d1?1:this._max,this._min=null!==this.overrideMin?this.overrideMin:this._min,this._max=null!==this.overrideMax?this.overrideMax:this._max,"x"===this.position){if(this._hasTimeField()?this._scale=d3.time.scale().rangeRound([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain([this._min,this._max]):this.useLog?this._scale=d3.scale.log().range([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(!0).base(this.logBase).nice():null===this.measure||void 0===this.measure?(f=g(this,"x","y"),this._scale=d3.scale.ordinal().rangePoints([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain(f.concat([""]))):this._scale=d3.scale.linear().range([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain([this._min,this._max]).nice(),!this.hidden)switch(this.chart._axisIndex(this,"x")){case 0:this._draw=d3.svg.axis().orient("bottom").scale(this._scale);break;case 1:this._draw=d3.svg.axis().orient("top").scale(this._scale)}}else if("y"===this.position){if(this._hasTimeField()?this._scale=d3.time.scale().rangeRound([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain([this._min,this._max]):this.useLog?this._scale=d3.scale.log().range([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(!0).base(this.logBase).nice():null===this.measure||void 0===this.measure?(f=g(this,"y","x"),this._scale=d3.scale.ordinal().rangePoints([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain(f.concat([""]))):this._scale=d3.scale.linear().range([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain([this._min,this._max]).nice(),!this.hidden)switch(this.chart._axisIndex(this,"y")){case 0:this._draw=d3.svg.axis().orient("left").scale(this._scale);break;case 1:this._draw=d3.svg.axis().orient("right").scale(this._scale)}}else this.position.length>0&&"z"===this.position[0]?this._scale=this.useLog?d3.scale.log().range([this.chart._heightPixels()/300,this.chart._heightPixels()/10]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(!0).base(this.logBase):d3.scale.linear().range([this.chart._heightPixels()/300,this.chart._heightPixels()/10]).domain([this._min,this._max]):this.position.length>0&&"c"===this.position[0]&&(this._scale=d3.scale.linear().range([0,null===this.colors||1===this.colors.length?1:this.colors.length-1]).domain([this._min,this._max]));return null!==a&&void 0!==a&&a!==!1||this._hasTimeField()||null===this._scale||null===this._scale.ticks||void 0===this._scale.ticks||!(this._scale.ticks(10).length>0)||"x"!==this.position&&"y"!==this.position||(b=this._scale.ticks(10),c=b[1]-b[0],d=((this._max-this._min)%c).toFixed(0),0!==d&&(this._max=Math.ceil(this._max/c)*c,this._min=Math.floor(this._min/c)*c,this._update(!0))),e=null!==f&&void 0!==f&&f.length>0?this._scale.copy()(f[0]):this._scale.copy()(0),this._origin!==e&&(this._previousOrigin=null===this._origin?e:this._origin,this._origin=e),this},this.addGroupOrderRule=function(a,b){this._groupOrderRules.push({ordering:a,desc:b})},this.addOrderRule=function(a,b){this._orderRules.push({ordering:a,desc:b})}},dimple.chart=function(a,b){this.svg=a,this.x="10%",this.y="10%",this.width="80%",this.height="80%",this.data=b,this.noFormats=!1,this.axes=[],this.series=[],this.legends=[],this.storyboard=null,this.titleShape=null,this.shapes=null,this._group=a.append("g"),this._tooltipGroup=null,this._assignedColors={},this._nextColor=0,this._axisIndex=function(a,b){var c=0,d=0,e=-1;for(c=0;c0&&(n=this.storyboard.categoryFields[0],o=dimple._getOrderedList(this.data,n,this.storyboard._orderRules)),a.x._hasCategories()&&a.x._hasMeasure()&&(r=a.x.categoryFields[0],s=dimple._getOrderedList(this.data,r,a.x._orderRules.concat([{ordering:a.x.measure,desc:!0}]))),a.y._hasCategories()&&a.y._hasMeasure()&&(t=a.y.categoryFields[0],u=dimple._getOrderedList(this.data,t,a.y._orderRules.concat([{ordering:a.y.measure,desc:!0}]))),null!==a.categoryFields&&void 0!==a.categoryFields&&a.categoryFields.length>0&&(v=[].concat(a._orderRules),p=a.categoryFields[0],null!==a.c&&void 0!==a.c&&a.c._hasMeasure()?v.push({ordering:a.c.measure,desc:!0}):null!==a.z&&void 0!==a.z&&a.z._hasMeasure()?v.push({ordering:a.z.measure,desc:!0}):a.x._hasMeasure()?v.push({ordering:a.x.measure,desc:!0}):a.y._hasMeasure()&&v.push({ordering:a.y.measure,desc:!0}),q=dimple._getOrderedList(this.data,p,v)),w.sort(function(a,b){var c=0;return""!==n&&(c=o.indexOf(a[n])-o.indexOf(b[n])),""!==r&&0===c&&(c=s.indexOf(a[r])-s.indexOf(b[r])),""!==t&&0===c&&(c=u.indexOf(a[t])-u.indexOf(b[t])),""!==p&&0===c&&(c=q.indexOf(a[p])-q.indexOf(b[p])),c}),w.forEach(function(b){var c,g,h,i,j=-1,k=e(a.x,b),l=e(a.y,b),m=e(a.z,b),n=[];for(null===a.categoryFields||void 0===a.categoryFields||0===a.categoryFields.length?n=["All"]:1===a.categoryFields.length&&void 0===b[a.categoryFields[0]]?n=[a.categoryFields[0]]:a.categoryFields.forEach(function(a){n.push(b[a])},this),c=n.join("/")+"_"+k.join("/")+"_"+l.join("/")+"_"+m.join("/"),g=0;g0&&(m+="/"),m+=b[a],i=m===g},this)),null!==c&&void 0!==c&&i&&(h=d[j],c._hasMeasure()&&(void 0===b[c.measure]&&(b[c.measure]=0),-1===h[c.position+"ValueList"].indexOf(b[c.measure])&&h[c.position+"ValueList"].push(b[c.measure]),isNaN(parseFloat(b[c.measure]))&&(f[c.position]=!0),k.value=h[c.position+"Value"],k.count=h[c.position+"Count"],l.value=b[c.measure],h[c.position+"Value"]=a.aggregate(k,l),h[c.position+"Count"]+=1))},i(a.x,this.storyboard),i(a.y,this.storyboard),i(a.z,this.storyboard),i(a.c,this.storyboard)},this),null!==a.x&&void 0!==a.x&&a.x._hasCategories()&&a.x.categoryFields.length>1&&void 0!==g.x&&(x=[],a.y._hasMeasure()&&x.push({ordering:a.y.measure,desc:!0}),g.x=dimple._getOrderedList(this.data,a.x.categoryFields[1],a.x._groupOrderRules.concat(x))),null!==a.y&&void 0!==a.y&&a.y._hasCategories()&&a.y.categoryFields.length>1&&void 0!==g.y&&(x=[],a.x._hasMeasure()&&x.push({ordering:a.x.measure,desc:!0}),g.y=dimple._getOrderedList(this.data,a.y.categoryFields[1],a.y._groupOrderRules.concat(x)),g.y.reverse()),d.forEach(function(c){null!==a.x&&(f.x===!0&&(c.xValue=c.xValueList.length),b=(null===h.x[c.xField.join("/")]||void 0===h.x[c.xField.join("/")]?0:h.x[c.xField.join("/")])+(a.y._hasMeasure()?Math.abs(c.yValue):0),h.x[c.xField.join("/")]=b),null!==a.y&&(f.y===!0&&(c.yValue=c.yValueList.length),b=(null===h.y[c.yField.join("/")]||void 0===h.y[c.yField.join("/")]?0:h.y[c.yField.join("/")])+(a.x._hasMeasure()?Math.abs(c.xValue):0),h.y[c.yField.join("/")]=b),null!==a.z&&(f.z===!0&&(c.zValue=c.zValueList.length),b=(null===h.z[c.zField.join("/")]||void 0===h.z[c.zField.join("/")]?0:h.z[c.zField.join("/")])+(a.z._hasMeasure()?Math.abs(c.zValue):0),h.z[c.zField.join("/")]=b),null!==a.c&&((null===i.min||c.cValuei.max)&&(i.max=c.cValue))},this);for(c in h.x)h.x.hasOwnProperty(c)&&(m.x+=h.x[c]);for(c in h.y)h.y.hasOwnProperty(c)&&(m.y+=h.y[c]);for(c in h.z)h.z.hasOwnProperty(c)&&(m.z+=h.z[c]);d.forEach(function(b){var c,d,e,f,n,o=function(c,d,e){var f,i,n,o,p;null!==c&&void 0!==c&&(o=c.position,c._hasCategories()?c._hasMeasure()?(f=b[c.position+"Field"].join("/"),i=c.showPercent?h[c.position][f]/m[c.position]:h[c.position][f],-1===k.indexOf(f)&&(l[f]=i+(k.length>0?l[k[k.length-1]]:0),k.push(f)),n=b[o+"Bound"]=b["c"+o]="x"!==o&&"y"!==o||!a.stacked?i:l[f],b[e]=i,b[o]=n-("x"===o&&i>=0||"y"===o&&0>=i?i:0)):(b[o]=b["c"+o]=b[o+"Field"][0],b[e]=1,void 0!==g[o]&&null!==g[o]&&g[o].length>=2&&(b[o+"Offset"]=g[o].indexOf(b[o+"Field"][1]),b[e]=1/g[o].length)):(i=c.showPercent?b[o+"Value"]/h[d][b[d+"Field"].join("/")]:b[o+"Value"],f=b[d+"Field"].join("/")+(b[o+"Value"]>=0),p=j[o][f]=(null===j[o][f]||void 0===j[o][f]||"z"===o?0:j[o][f])+i,n=b[o+"Bound"]=b["c"+o]="x"!==o&&"y"!==o||!a.stacked?i:p,b[e]=i,b[o]=n-("x"===o&&i>=0||"y"===o&&0>=i?i:0)))};o(a.x,"y","width"),o(a.y,"x","height"),o(a.z,"z","r"),null!==a.c&&null!==i.min&&null!==i.max&&(i.min===i.max&&(i.min-=.5,i.max+=.5),i.min=null!==a.c.overrideMin&&void 0!==a.c.overrideMin?a.c.overrideMin:i.min,i.max=null!==a.c.overrideMax&&void 0!==a.c.overrideMax?a.c.overrideMax:i.max,b.cValue=b.cValue>i.max?i.max:b.cValue1?(c=d3.rgb(a.c.colors[Math.floor(f)]),d=d3.rgb(a.c.colors[Math.ceil(f)])):(c=d3.rgb("white"),d=d3.rgb(this.getColor(b.aggField.slice(-1)[0]).fill)),c.r=Math.floor(c.r+(d.r-c.r)*n),c.g=Math.floor(c.g+(d.g-c.g)*n),c.b=Math.floor(c.b+(d.b-c.b)*n),b.fill=c.toString(),b.stroke=c.darker(.5).toString())},this),a._positionData=d},this)},this._heightPixels=function(){return dimple._parseYPosition(this.height,this.svg.node())},this._registerEventHandlers=function(a){null!==a._eventHandlers&&a._eventHandlers.length>0&&a._eventHandlers.forEach(function(b){null!==b.handler&&"function"==typeof b.handler&&a.shapes.on(b.event,function(c){var d=new dimple.eventArgs;null!==a.chart.storyboard&&(d.frameValue=a.chart.storyboard.getFrameValue()),d.seriesValue=c.aggField,d.xValue=c.x,d.yValue=c.y,d.zValue=c.z,d.colorValue=c.cValue,d.seriesShapes=a.shapes,d.selectedShape=d3.select(this),b.handler(d)})},this)},this._widthPixels=function(){return dimple._parseXPosition(this.width,this.svg.node())},this._xPixels=function(){return dimple._parseXPosition(this.x,this.svg.node())},this._yPixels=function(){return dimple._parseYPosition(this.y,this.svg.node())},this.addAxis=function(a,b,c,d){null!==b&&void 0!==b&&(b=[].concat(b));var e=new dimple.axis(this,a,b,c,d);return this.axes.push(e),e},this.addCategoryAxis=function(a,b){return this.addAxis(a,b,null)},this.addColorAxis=function(a,b){var c=this.addAxis("c",null,a);return c.colors=null===b||void 0===b?null:[].concat(b),c},this.addLegend=function(a,b,c,d,e,f){f=null===f||void 0===f?this.series:[].concat(f),e=null===e||void 0===e?"left":e;var g=new dimple.legend(this,a,b,c,d,e,f);return this.legends.push(g),g},this.addLogAxis=function(a,b,c){var d=this.addAxis(a,null,b,null);return null!==c&&void 0!==c&&(d.logBase=c),d.useLog=!0,d},this.addMeasureAxis=function(a,b){return this.addAxis(a,null,b)},this.addPctAxis=function(a,b,c){var d=null;return d=null!==c&&void 0!==c?this.addAxis(a,c,b):this.addMeasureAxis(a,b),d.showPercent=!0,d},this.addSeries=function(a,b,c){(null===c||void 0===c)&&(c=this.axes),(null===b||void 0===b)&&(b=dimple.plot.bubble);var d,e=null,f=null,g=null,h=null;return c.forEach(function(a){null!==a&&b.supportedAxes.indexOf(a.position)>-1&&(null===e&&"x"===a.position[0]?e=a:null===f&&"y"===a.position[0]?f=a:null===g&&"z"===a.position[0]?g=a:null===h&&"c"===a.position[0]&&(h=a))},this),null!==a&&void 0!==a&&(a=[].concat(a)),d=new dimple.series(this,a,e,f,g,h,b,dimple.aggregateMethod.sum,b.stacked),this.series.push(d),d},this.addTimeAxis=function(a,b,c,d){var e=this.addAxis(a,null,null,b);return e.tickFormat=d,e.dateParseFormat=c,e},this.assignColor=function(a,b,c,d){return this._assignedColors[a]=new dimple.color(b,c,d),this._assignedColors[a]},this.defaultColors=[new dimple.color("#80B1D3"),new dimple.color("#FB8072"),new dimple.color("#FDB462"),new dimple.color("#B3DE69"),new dimple.color("#FFED6F"),new dimple.color("#BC80BD"),new dimple.color("#8DD3C7"),new dimple.color("#CCEBC5"),new dimple.color("#FFFFB3"),new dimple.color("#BEBADA"),new dimple.color("#FCCDE5"),new dimple.color("#D9D9D9")],this.draw=function(a,b){a=null===a||void 0===a?0:a;var c,d=null,e=null,f=!1,g=!1,h=this._xPixels(),i=this._yPixels(),j=this._widthPixels(),k=this._heightPixels();return(void 0===b||null===b||b===!1)&&this._getSeriesData(),this.axes.forEach(function(a){if(a._min=0,a._max=0,a._hasMeasure()){var b=!1;this.series.forEach(function(c){if(c[a.position]===a){var d=c._axisBounds(a.position);a._min>d.min&&(a._min=d.min),a._maxb[a.measure]&&(a._min=b[a.measure]),a._maxa._max)&&(a._max=c)},this)):a._hasCategories()&&(a._min=0,c=[],this.data.forEach(function(b){-1===c.indexOf(b[a.categoryFields[0]])&&c.push(b[a.categoryFields[0]])},this),a._max=c.length);a._update(),null===d&&"x"===a.position?d=a:null===e&&"y"===a.position&&(e=a)},this),this.axes.forEach(function(b){var c,l=!1,m=null,n=0,o=null,p=!1,q=0,r={l:null,t:null,r:null,b:null},s=0,t=0,u="",v=this;null===b.gridlineShapes?(b.showGridlines||null===b.showGridlines&&!b._hasCategories()&&(!f&&"x"===b.position||!g&&"y"===b.position))&&(b.gridlineShapes=this._group.append("g").attr("class","gridlines"),"x"===b.position?f=!0:g=!0):"x"===b.position?f=!0:g=!0,null===b.shapes&&(b.shapes=this._group.append("g").attr("class","axis"),l=!0),b===d&&null!==e?(m="translate(0, "+(null===e.categoryFields||0===e.categoryFields.length?e._scale(0):i+k)+")",o="translate(0, "+(b===d?i+k:i)+")",n=-k):b===e&&null!==d?(m="translate("+(null===d.categoryFields||0===d.categoryFields.length?d._scale(0):h)+", 0)",o="translate("+(b===e?h:h+j)+", 0)",n=-j):"x"===b.position?(o=m="translate(0, "+(b===d?i+k:i)+")",n=-k):"y"===b.position&&(o=m="translate("+(b===e?h:h+j)+", 0)",n=-j),c=function(b){var c;return c=null===m||0===a||l?b:b.transition().duration(a)},null!==m&&null!==b._draw&&(b._hasTimeField()?c(b.shapes).call(b._draw.ticks(b._getTimePeriod(),b.timeInterval).tickFormat(b._getFormat())).attr("transform",m):b.useLog?c(b.shapes).call(b._draw.ticks(4,b._getFormat())).attr("transform",m):c(b.shapes).call(b._draw.tickFormat(b._getFormat())).attr("transform",m),null!==b.gridlineShapes&&c(b.gridlineShapes).call(b._draw.tickSize(n,0,0).tickFormat("")).attr("transform",o),(null===b.measure||void 0===b.measure)&&("x"===b.position?c(b.shapes.selectAll("text")).attr("x",j/b._max/2):"y"===b.position&&c(b.shapes.selectAll("text")).attr("y",-1*(k/b._max)/2)),null!==b.categoryFields&&void 0!==b.categoryFields&&b.categoryFields.length>0&&(b!==d||null!==e.categoryFields&&0!==e.categoryFields.length||c(b.shapes.selectAll("text")).attr("y",i+k-e._scale(0)+9),b!==e||null!==d.categoryFields&&0!==d.categoryFields.length||c(b.shapes.selectAll("text")).attr("x",-1*(d._scale(0)-h)-9))),this.noFormats||(c(b.shapes.selectAll("text")).style("font-family","sans-serif").style("font-size",(k/35>10?k/35:10)+"px"),c(b.shapes.selectAll("path, line")).style("fill","none").style("stroke","black").style("shape-rendering","crispEdges"),null!==b.gridlineShapes&&c(b.gridlineShapes.selectAll("line")).style("fill","none").style("stroke","lightgray").style("opacity",.8)),(null===b.measure||void 0===b.measure)&&(b===d?(q=0,b.shapes.selectAll("text").each(function(){var a=this.getComputedTextLength();q=a>q?a:q}),q>j/b.shapes.selectAll("text")[0].length?(p=!0,b.shapes.selectAll("text").style("text-anchor","start").each(function(){var a=this.getBBox();d3.select(this).attr("transform","rotate(90,"+a.x+","+(a.y+a.height/2)+") translate(-5, 0)")})):(p=!1,b.shapes.selectAll("text").style("text-anchor","middle").attr("transform",""))):"x"===b.position&&(q=0,b.shapes.selectAll("text").each(function(){var a=this.getComputedTextLength();q=a>q?a:q}),q>j/b.shapes.selectAll("text")[0].length?(p=!0,b.shapes.selectAll("text").style("text-anchor","end").each(function(){var a=this.getBBox();d3.select(this).attr("transform","rotate(90,"+(a.x+a.width)+","+(a.y+a.height/2)+") translate(5, 0)")})):(p=!1,b.shapes.selectAll("text").style("text-anchor","middle").attr("transform","")))),null!==b.titleShape&&void 0!==b.titleShape&&b.titleShape.remove(),b.shapes.selectAll("text").each(function(){var a=this.getBBox();(null===r.l||-9-a.widthr.r)&&(r.r=a.x+a.width),p?((null===r.t||a.y+a.height-a.widthr.b)&&(r.b=a.height+a.width)):((null===r.t||a.yr.b)&&(r.b=9+a.height))}),"x"===b.position?(t=b===d?i+k+r.b+5:i+r.t-10,s=h+j/2):"y"===b.position&&(s=b===e?h+r.l-10:h+j+r.r+20,t=i+k/2,u="rotate(270, "+s+", "+t+")"),b.hidden||"x"!==b.position&&"y"!==b.position||(b.titleShape=this._group.append("text").attr("class","axis title"),b.titleShape.attr("x",s).attr("y",t).attr("text-anchor","middle").attr("transform",u).text(null===b.categoryFields||void 0===b.categoryFields||0===b.categoryFields.length?b.measure:b.categoryFields.join("/")).each(function(){v.noFormats||d3.select(this).style("font-family","sans-serif").style("font-size",(k/35>10?k/35:10)+"px")}),b===d?b.titleShape.each(function(){d3.select(this).attr("y",t+this.getBBox().height/1.65)}):b===e&&b.titleShape.each(function(){d3.select(this).attr("x",s+this.getBBox().height/1.65)}))},this),this.series.forEach(function(b){b.plot.draw(this,b,a),this._registerEventHandlers(b)},this),this.legends.forEach(function(b){b._draw(a)},this),null!==this.storyboard&&void 0!==this.storyboard&&(this.storyboard._drawText(),this.storyboard.autoplay&&this.storyboard.startAnimation()),this},this.getColor=function(a){return(null===this._assignedColors[a]||void 0===this._assignedColors[a])&&(this._assignedColors[a]=this.defaultColors[this._nextColor],this._nextColor=(this._nextColor+1)%this.defaultColors.length),this._assignedColors[a]},this.setBounds=function(a,b,c,d){return this.x=a,this.y=b,this.width=c,this.height=d,this._xPixels=function(){return dimple._parseXPosition(this.x,this.svg.node())},this._yPixels=function(){return dimple._parseYPosition(this.y,this.svg.node())},this._widthPixels=function(){return dimple._parseXPosition(this.width,this.svg.node())},this._heightPixels=function(){return dimple._parseYPosition(this.height,this.svg.node())},this.draw(0,!0),this},this.setMargins=function(a,b,c,d){return this.x=a,this.y=b,this.width=0,this.height=0,this._xPixels=function(){return dimple._parseXPosition(this.x,this.svg.node())},this._yPixels=function(){return dimple._parseYPosition(this.y,this.svg.node())},this._widthPixels=function(){return dimple._parentWidth(this.svg.node())-this._xPixels()-dimple._parseXPosition(c,this.svg.node())},this._heightPixels=function(){return dimple._parentHeight(this.svg.node())-this._yPixels()-dimple._parseYPosition(d,this.svg.node())},this.draw(0,!0),this},this.setStoryboard=function(a,b){return this.storyboard=new dimple.storyboard(this,a),null!==b&&void 0!==b&&(this.storyboard.onTick=b),this.storyboard}},dimple.color=function(a,b,c){this.fill=a,this.stroke=null===b||void 0===b?d3.rgb(a).darker(.5).toString():b,this.opacity=null===c||void 0===c?.8:c},dimple.eventArgs=function(){this.seriesValue=null,this.xValue=null,this.yValue=null,this.zValue=null,this.colorValue=null,this.frameValue=null,this.seriesShapes=null,this.selectedShape=null},dimple.legend=function(a,b,c,d,e,f,g){this.chart=a,this.series=g,this.x=b,this.y=c,this.width=d,this.height=e,this.horizontalAlign=f,this.shapes=null,this._draw=function(a){var b,c=this._getEntries(),d=0,e=0,f=0,g=0,h=15,i=9,j=this;null!==this.shapes&&void 0!==this.shapes&&this.shapes.transition().duration(.2*a).attr("opacity",0).remove(),b=this.chart._group.selectAll(".dontSelectAny").data(c).enter().append("g").attr("class","legend").attr("opacity",0),b.append("text").attr("id",function(a){return"legend_"+a.key}).attr("class","legendText").text(function(a){return a.key}).call(function(){j.chart.noFormats||this.style("font-family","sans-serif").style("font-size",(j.chart._heightPixels()/35>10?j.chart._heightPixels()/35:10)+"px").style("shape-rendering","crispEdges")}).each(function(){var a=this.getBBox();a.width>d&&(d=a.width),a.height>e&&(e=a.height)}),b.append("rect").attr("class","legendKey").attr("height",i).attr("width",h),e=(i>e?i:e)+2,d+=h+20,b.each(function(a){f+d>j._widthPixels()&&(f=0,g+=e),g>j._heightPixels()?d3.select(this).remove():(d3.select(this).select("text").attr("x","left"===j.horizontalAlign?j._xPixels()+h+5+f:j._xPixels()+(j._widthPixels()-f-d)+h+5).attr("y",function(){return j._yPixels()+g+this.getBBox().height/1.65}).attr("width",j._widthPixels()).attr("height",j._heightPixels()),d3.select(this).select("rect").attr("class","legend legendKey").attr("x","left"===j.horizontalAlign?j._xPixels()+f:j._xPixels()+(j._widthPixels()-f-d)).attr("y",j._yPixels()+g).attr("height",i).attr("width",h).style("fill",function(){return dimple._helpers.fill(a,j.chart,a.series)}).style("stroke",function(){return dimple._helpers.stroke(a,j.chart,a.series)}).style("opacity",function(){return dimple._helpers.opacity(a,j.chart,a.series)}).style("shape-rendering","crispEdges"),f+=d)}),b.transition().delay(.2*a).duration(.8*a).attr("opacity",1),this.shapes=b},this._getEntries=function(){var a=[];return null!==this.series&&void 0!==this.series&&this.series.forEach(function(b){var c=b._positionData;c.forEach(function(c){var d,e=-1;for(d=0;de.max&&(e.max=a[f.position+"Bound"])},this):null===g||null===g.categoryFields||0===g.categoryFields.length?j.forEach(function(a){!this.stacked||"x"!==f.position&&"y"!==f.position?(a[f.position+"Value"]e.max&&(e.max=a[f.position+"Value"])):a[f.position+"Value"]<0?e.min=e.min+a[f.position+"Value"]:e.max=e.max+a[f.position+"Value"]},this):(b=f.position+"Value",c=g.position+"Field",d=[],j.forEach(function(a){var e=a[c].join("/"),f=d.indexOf(e);-1===f&&(d.push(e),f=d.length-1),void 0===h[f]&&(h[f]={min:0,max:0},f>=i&&(i=f+1)),this.stacked?a[b]<0?h[f].min=h[f].min+a[b]:h[f].max=h[f].max+a[b]:(a[b]h[f].max&&(h[f].max=a[b]))},this),h.forEach(function(a){void 0!==a&&(a.mine.max&&(e.max=a.max))},this)),e},this._dropLineOrigin=function(){var a=0,b=0,c={x:null,y:null},d={x:null,y:null};return this.chart.axes.forEach(function(a){"x"===a.position&&null===d.x?d.x=a._hasTimeField()?this.chart._xPixels():a._origin:"y"===a.position&&null===d.y&&(d.y=a._hasTimeField()?this.chart._yPixels()+this.chart._heightPixels():a._origin)},this),this.chart.axes.forEach(function(e){"x"!==e.position||this.x.hidden?"y"!==e.position||this.y.hidden||(e===this.y&&(0===b?c.x=d.x:1===b&&(c.x=this.chart._xPixels()+this.chart._widthPixels())),b+=1):(e===this.x&&(0===a?c.y=d.y:1===a&&(c.y=this.chart._yPixels())),a+=1)},this),c},this.addEventHandler=function(a,b){this._eventHandlers.push({event:a,handler:b})},this.addOrderRule=function(a,b){this._orderRules.push({ordering:a,desc:b})}},dimple.storyboard=function(a,b){null!==b&&void 0!==b&&(b=[].concat(b)),this.chart=a,this.categoryFields=b,this.autoplay=!0,this.frameDuration=3e3,this.storyLabel=null,this.onTick=null,this._frame=0,this._animationTimer=null,this._categories=[],this._cachedCategoryFields=[],this._orderRules=[],this._drawText=function(a){if(null===this.storyLabel||void 0===this.storyLabel){var b=this.chart,c=0;this.chart.axes.forEach(function(a){"x"===a.position&&(c+=1)},this),this.storyLabel=this.chart._group.append("text").attr("x",this.chart._xPixels()+.01*this.chart._widthPixels()).attr("y",this.chart._yPixels()+(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)*(c>1?1.25:-1)).call(function(){b.noFormats||this.style("font-family","sans-serif").style("font-size",(b._heightPixels()/35>10?b._heightPixels()/35:10)+"px")})}this.storyLabel.transition().duration(.2*a).attr("opacity",0),this.storyLabel.transition().delay(.2*a).text(this.categoryFields.join("\\")+": "+this.getFrameValue()).transition().duration(.8*a).attr("opacity",1)},this._getCategories=function(){return this._categoryFields!==this._cachedCategoryFields&&(this._categories=[],this.chart.data.forEach(function(a){var b=-1,c="";null!==this.categoryFields&&(this.categoryFields.forEach(function(b,d){d>0&&(c+="/"),c+=a[b]},this),b=this._categories.indexOf(c),-1===b&&(this._categories.push(c),b=this._categories.length-1))},this),this._cachedCategoryFields=this._categoryFields),this._categories},this._goToFrameIndex=function(a){this._frame=a%this._getCategories().length,this.chart.draw(this.frameDuration/2)},this.addOrderRule=function(a,b){this._orderRules.push({ordering:a,desc:b})},this.getFrameValue=function(){var a=null;return this._frame>=0&&this._getCategories().length>this._frame&&(a=this._getCategories()[this._frame]),a},this.goToFrame=function(a){if(this._getCategories().length>0){var b=this._getCategories().indexOf(a);this._goToFrameIndex(b)}},this.pauseAnimation=function(){null!==this._animationTimer&&(window.clearInterval(this._animationTimer),this._animationTimer=null)},this.startAnimation=function(){null===this._animationTimer&&(null!==this.onTick&&this.onTick(this.getFrameValue()),this._animationTimer=window.setInterval(function(a){return function(){a._goToFrameIndex(a._frame+1),null!==a.onTick&&a.onTick(a.getFrameValue()),a._drawText(a.frameDuration/2)}}(this),this.frameDuration))},this.stopAnimation=function(){null!==this._animationTimer&&(window.clearInterval(this._animationTimer),this._animationTimer=null,this._frame=0)}},dimple.aggregateMethod.avg=function(a,b){return a.value=null===a.value||void 0===a.value?0:parseFloat(a.value),a.count=null===a.count||void 0===a.count?1:parseFloat(a.count),b.value=null===b.value||void 0===b.value?0:parseFloat(b.value),b.count=null===b.count||void 0===b.count?1:parseFloat(b.count),(a.value*a.count+b.value*b.count)/(a.count+b.count)},dimple.aggregateMethod.count=function(a,b){return a.count=null===a.count||void 0===a.count?0:parseFloat(a.count),b.count=null===b.count||void 0===b.count?0:parseFloat(b.count),a.count+b.count},dimple.aggregateMethod.max=function(a,b){return a.value=null===a.value||void 0===a.value?0:parseFloat(a.value),b.value=null===b.value||void 0===b.value?0:parseFloat(b.value),a.value>b.value?a.value:b.value},dimple.aggregateMethod.min=function(a,b){return null===a.value?parseFloat(b.value):parseFloat(a.value)=0;i-=1)if(f=e[i],h={cx:0,cy:0,height:0,width:0,xOffset:0,yOffset:0},b.x._hasCategories()){if(h.cx=f.cx,h.width=f.width,h.xOffset=f.xOffset,void 0===k[f.xField])k[f.xField]=[];else{for(p=0,j=0;j<=k[f.xField].length;j+=1)m=k[f.xField][j],(f.cy>=0&&m>=0||f.cy<=0&&0>=m)&&Math.abs(m)<=Math.abs(f.cy)&&Math.abs(m)>Math.abs(p)&&(p=m);h.cy=p}o.push(h),k[f.xField].push(f.cy)}else if(b.y._hasCategories()){if(h.cy=f.cy,h.height=f.height,h.yOffset=f.yOffset,void 0===k[f.yField])k[f.yField]=[];else{for(p=0,l=0;l<=k[f.yField].length;l+=1)n=k[f.yField][l],(f.cx>=0&&n>=0||f.cx<=0&&0>=n)&&Math.abs(n)<=Math.abs(f.cx)&&Math.abs(n)>Math.abs(p)&&(p=n);h.cx=p}o.push(h),k[f.yField].push(f.cx)}return d(e.concat(o).concat(e[0]))}).call(function(){a.noFormats||this.attr("fill",function(b){return j?"url(#fill-area-gradient-"+b.join("_").replace(" ","")+")":a.getColor(b).fill}).attr("stroke",function(b){return j?"url(#stroke-area-gradient-"+b.join("_").replace(" ","")+")":a.getColor(b).stroke}).attr("stroke-width",b.lineWeight)}),e=a._group.selectAll(".markers").data(g).enter(),b.lineMarkers&&e.append("circle").transition().duration(c).attr("cx",function(c){return dimple._helpers.cx(c,a,b)}).attr("cy",function(c){return dimple._helpers.cy(c,a,b)}).attr("r",2+b.lineWeight).attr("fill","white").attr("stroke","none"),e.append("circle").on("mouseover",function(c){f.enterEventHandler(c,this,a,b)}).on("mouseleave",function(c){f.leaveEventHandler(c,this,a,b)}).transition().duration(c).attr("cx",function(c){return dimple._helpers.cx(c,a,b)}).attr("cy",function(c){return dimple._helpers.cy(c,a,b)}).attr("r",2+b.lineWeight).attr("opacity",function(c){return b.lineMarkers?a.getColor(c).opacity:0}).call(function(){a.noFormats||this.attr("fill","white").style("stroke-width",b.lineWeight).attr("stroke",function(c){return dimple._helpers.stroke(c,a,b)})})},enterEventHandler:function(a,b,c,d){var e,f,g,h=5,i=10,j=750,k=d3.select(b),l=parseFloat(k.attr("cx")),m=parseFloat(k.attr("cy")),n=parseFloat(k.attr("r")),o=dimple._helpers.opacity(a,c,d),p=dimple._helpers.fill(a,c,d),q=d._dropLineOrigin(),r=d3.rgb(d3.rgb(p).r+.6*(255-d3.rgb(p).r),d3.rgb(p).g+.6*(255-d3.rgb(p).g),d3.rgb(p).b+.6*(255-d3.rgb(p).b)),s=d3.rgb(d3.rgb(p).r+.8*(255-d3.rgb(p).r),d3.rgb(p).g+.8*(255-d3.rgb(p).g),d3.rgb(p).b+.8*(255-d3.rgb(p).b)),t=0,u=0,v=0,w=[];null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove(),c._tooltipGroup=c.svg.append("g"),k.style("opacity",1),c._tooltipGroup.append("circle").attr("cx",l).attr("cy",m).attr("r",n).attr("opacity",0).style("fill","none").style("stroke",p).style("stroke-width",1).transition().duration(j/2).ease("linear").attr("opacity",1).attr("r",n+4).style("stroke-width",2),null!==q.y&&c._tooltipGroup.append("line").attr("x1",l).attr("y1",m0&&d.categoryFields.forEach(function(b,c){w.push(b+(a.aggField[c]!==b?": "+a.aggField[c]:""))},this),d.x._hasTimeField()?w.push(d.x.timeField+": "+d.x._getFormat()(a.xField[0])):d.x._hasCategories()?d.x.categoryFields.forEach(function(b,c){w.push(b+(a.xField[c]!==b?": "+a.xField[c]:""))},this):w.push(d.x.measure+": "+d.x._getFormat()(a.width)),d.y._hasTimeField()?w.push(d.y.timeField+": "+d.y._getFormat()(a.yField[0])):d.y._hasCategories()?d.y.categoryFields.forEach(function(b,c){w.push(b+(a.yField[c]!==b?": "+a.yField[c]:""))},this):w.push(d.y.measure+": "+d.y._getFormat()(a.height)),null!==d.z&&void 0!==d.z&&w.push(d.z.measure+": "+d.z._getFormat()(a.zValue)),null!==d.c&&void 0!==d.c&&w.push(d.c.measure+": "+d.c._getFormat()(a.cValue)),w=w.filter(function(a,b){return w.indexOf(a)===b}),e.selectAll(".textHoverShapes").data(w).enter().append("text").text(function(a){return a}).style("font-family","sans-serif").style("font-size","10px"),e.each(function(){u=this.getBBox().width>u?this.getBBox().width:u,v=this.getBBox().width>v?this.getBBox().height:v}),e.selectAll("text").attr("x",0).attr("y",function(){return t+=this.getBBox().height,t-this.getBBox().height/2}),f.attr("x",-h).attr("y",-h).attr("height",Math.floor(t+h)-.5).attr("width",u+2*h).attr("rx",5).attr("ry",5).style("fill",s).style("stroke",r).style("stroke-width",2).style("opacity",.95),g=l+n+h+i+u>parseFloat(c.svg.attr("width")),e.attr("transform","translate("+(g?l-(n+h+i+u):l+n+h+i)+" , "+(m-(t-(v-h))/2)+")")},leaveEventHandler:function(a,b,c,d){d3.select(b).style("opacity",d.lineMarkers?dimple._helpers.opacity(a,c,d):0),null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove()}},dimple.plot.bar={stacked:!0,supportedAxes:["x","y","c"],draw:function(a,b,c){var d=this,e=b._positionData,f=null,g="series"+a.series.indexOf(b);null!==a._tooltipGroup&&void 0!==a._tooltipGroup&&a._tooltipGroup.remove(),f=null===b.shapes||void 0===b.shapes?a._group.selectAll("."+g).data(e):b.shapes.data(e,function(a){return a.key}),f.enter().append("rect").attr("id",function(a){return a.key}).attr("class",function(a){return g+" bar "+a.aggField.join(" ")+" "+a.xField.join(" ")+" "+a.yField.join(" ")}).attr("x",function(c){return dimple._helpers.x(c,a,b)}).attr("y",function(c){return dimple._helpers.y(c,a,b)+dimple._helpers.height(c,a,b)}).attr("width",function(c){return null!==c.xField&&c.xField.length>0?dimple._helpers.width(c,a,b):0}).attr("height",function(c){return null!==c.yField&&c.yField.length>0?dimple._helpers.height(c,a,b):0}).attr("opacity",function(c){return dimple._helpers.opacity(c,a,b)}).on("mouseover",function(c){d.enterEventHandler(c,this,a,b)}).on("mouseleave",function(){d.leaveEventHandler(a)}).call(function(){a.noFormats||this.attr("fill",function(c){return dimple._helpers.fill(c,a,b)}).attr("stroke",function(c){return dimple._helpers.stroke(c,a,b)})}),f.transition().duration(c).attr("x",function(c){return dimple._helpers.x(c,a,b)}).attr("y",function(c){return dimple._helpers.y(c,a,b)}).attr("width",function(c){return dimple._helpers.width(c,a,b)}).attr("height",function(c){return dimple._helpers.height(c,a,b)}).call(function(){a.noFormats||this.attr("fill",function(c){return dimple._helpers.fill(c,a,b)}).attr("stroke",function(c){return dimple._helpers.stroke(c,a,b)})}),f.exit().transition().duration(c).attr("x",function(c){return dimple._helpers.x(c,a,b)}).attr("y",function(c){return dimple._helpers.y(c,a,b)}).attr("width",function(c){return dimple._helpers.width(c,a,b)}).attr("height",function(c){return dimple._helpers.height(c,a,b)}).each("end",function(){d3.select(this).remove()}),b.shapes=f},enterEventHandler:function(a,b,c,d){var e,f,g=5,h=10,i=750,j=d3.select(b),k=parseFloat(j.attr("x")),l=parseFloat(j.attr("y")),m=parseFloat(j.attr("width")),n=parseFloat(j.attr("height")),o=j.attr("opacity"),p=j.attr("fill"),q=d._dropLineOrigin(),r=d3.rgb(d3.rgb(p).r+.6*(255-d3.rgb(p).r),d3.rgb(p).g+.6*(255-d3.rgb(p).g),d3.rgb(p).b+.6*(255-d3.rgb(p).b)),s=d3.rgb(d3.rgb(p).r+.8*(255-d3.rgb(p).r),d3.rgb(p).g+.8*(255-d3.rgb(p).g),d3.rgb(p).b+.8*(255-d3.rgb(p).b)),t=[],u=0,v=0,w=0;null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove(),c._tooltipGroup=c.svg.append("g"),d.x._hasCategories()||null===q.y||c._tooltipGroup.append("line").attr("x1",k0&&d.categoryFields.forEach(function(b,c){t.push(b+(a.aggField[c]!==b?": "+a.aggField[c]:""))},this),d.x._hasTimeField()?t.push(d.x.timeField+": "+d.x._getFormat()(a.xField[0])):d.x._hasCategories()?d.x.categoryFields.forEach(function(b,c){t.push(b+(a.xField[c]!==b?": "+a.xField[c]:""))},this):t.push(d.x.measure+": "+d.x._getFormat()(a.width)),d.y._hasTimeField()?t.push(d.y.timeField+": "+d.y._getFormat()(a.yField[0])):d.y._hasCategories()?d.y.categoryFields.forEach(function(b,c){t.push(b+(a.yField[c]!==b?": "+a.yField[c]:""))},this):t.push(d.y.measure+": "+d.y._getFormat()(a.height)),null!==d.c&&void 0!==d.c&&t.push(d.c.measure+": "+d.c._getFormat()(d.c.showPercent?a.cPct:a.cValue)),t=t.filter(function(a,b){return t.indexOf(a)===b}),e.selectAll(".textHoverShapes").data(t).enter().append("text").text(function(a){return a}).style("font-family","sans-serif").style("font-size","10px"),e.each(function(){v=this.getBBox().width>v?this.getBBox().width:v,w=this.getBBox().width>w?this.getBBox().height:w}),e.selectAll("text").attr("x",0).attr("y",function(){return u+=this.getBBox().height,u-this.getBBox().height/2}),f.attr("x",-g).attr("y",-g).attr("height",Math.floor(u+g)-.5).attr("width",v+2*g).attr("rx",5).attr("ry",5).style("fill",s).style("stroke",r).style("stroke-width",2).style("opacity",.95),k+m+g+h+v0?e.attr("transform","translate("+(k-(g+h+v))+" , "+(l+n/2-(u-(w-g))/2)+")"):l+n+u+h+g0&&d.categoryFields.forEach(function(b,c){t.push(b+(a.aggField[c]!==b?": "+a.aggField[c]:""))},this),d.x._hasTimeField()?t.push(d.x.timeField+": "+d.x._getFormat()(a.xField[0])):d.x._hasCategories()?d.x.categoryFields.forEach(function(b,c){t.push(b+(a.xField[c]!==b?": "+a.xField[c]:""))},this):t.push(d.x.measure+": "+d.x._getFormat()(a.cx)),d.y._hasTimeField()?t.push(d.y.timeField+": "+d.y._getFormat()(a.yField[0])):d.y._hasCategories()?d.y.categoryFields.forEach(function(b,c){t.push(b+(a.yField[c]!==b?": "+a.yField[c]:""))},this):t.push(d.y.measure+": "+d.y._getFormat()(a.cy)),null!==d.z&&void 0!==d.z&&t.push(d.z.measure+": "+d.z._getFormat()(a.zValue)),null!==d.c&&void 0!==d.c&&t.push(d.c.measure+": "+d.c._getFormat()(a.cValue)),t=t.filter(function(a,b){return t.indexOf(a)===b}),e.selectAll(".textHoverShapes").data(t).enter().append("text").text(function(a){return a}).style("font-family","sans-serif").style("font-size","10px"),e.each(function(){v=this.getBBox().width>v?this.getBBox().width:v,w=this.getBBox().width>w?this.getBBox().height:w}),e.selectAll("text").attr("x",0).attr("y",function(){return u+=this.getBBox().height,u-this.getBBox().height/2}),f.attr("x",-h).attr("y",-h).attr("height",Math.floor(u+h)-.5).attr("width",v+2*h).attr("rx",5).attr("ry",5).style("fill",s).style("stroke",r).style("stroke-width",2).style("opacity",.95),g=l+n+h+i+v>parseFloat(c.svg.attr("width")),e.attr("transform","translate("+(g?l-(n+h+i+v):l+n+h+i)+" , "+(m-(u-(w-h))/2)+")")},leaveEventHandler:function(a){null!==a._tooltipGroup&&void 0!==a._tooltipGroup&&a._tooltipGroup.remove()}},dimple.plot.line={stacked:!1,supportedAxes:["x","y","c"],draw:function(a,b,c){var d,e,f=this,g=b._positionData,h=[],i=[],j=1,k=!1;null!==a._tooltipGroup&&void 0!==a._tooltipGroup&&a._tooltipGroup.remove(),(b.x._hasCategories()||b.y._hasCategories())&&(j=0),g.forEach(function(a){var b,c=[],d=!1;for(b=j;b0&&d.categoryFields.forEach(function(b,c){w.push(b+(a.aggField[c]!==b?": "+a.aggField[c]:""))},this),d.x._hasTimeField()?w.push(d.x.timeField+": "+d.x._getFormat()(a.xField[0])):d.x._hasCategories()?d.x.categoryFields.forEach(function(b,c){w.push(b+(a.xField[c]!==b?": "+a.xField[c]:""))},this):d.x.useLog?w.push(d.x.measure+": "+a.cx):w.push(d.x.measure+": "+d.x._getFormat()(a.cx)),d.y._hasTimeField()?w.push(d.y.timeField+": "+d.y._getFormat()(a.yField[0])):d.y._hasCategories()?d.y.categoryFields.forEach(function(b,c){w.push(b+(a.yField[c]!==b?": "+a.yField[c]:""))},this):d.y.useLog?w.push(d.y.measure+": "+a.cy):w.push(d.y.measure+": "+d.y._getFormat()(a.cy)),null!==d.z&&void 0!==d.z&&w.push(d.z.measure+": "+d.z._getFormat()(a.zValue)),null!==d.c&&void 0!==d.c&&w.push(d.c.measure+": "+d.c._getFormat()(a.cValue)),w=w.filter(function(a,b){return w.indexOf(a)===b}),e.selectAll(".textHoverShapes").data(w).enter().append("text").text(function(a){return a}).style("font-family","sans-serif").style("font-size","10px"),e.each(function(){u=this.getBBox().width>u?this.getBBox().width:u,v=this.getBBox().width>v?this.getBBox().height:v}),e.selectAll("text").attr("x",0).attr("y",function(){return t+=this.getBBox().height,t-this.getBBox().height/2}),f.attr("x",-h).attr("y",-h).attr("height",Math.floor(t+h)-.5).attr("width",u+2*h).attr("rx",5).attr("ry",5).style("fill",s).style("stroke",r).style("stroke-width",2).style("opacity",.95),g=l+n+h+i+u>parseFloat(c.svg.attr("width")),e.attr("transform","translate("+(g?l-(n+h+i+u):l+n+h+i)+" , "+(m-(t-(v-h))/2)+")")},leaveEventHandler:function(a,b,c,d){d3.select(b).style("opacity",d.lineMarkers?dimple._helpers.opacity(a,c,d):0),null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove()}},dimple._addGradient=function(a,b,c,d,e,f,g){var h=e._group.select("#"+b),i=[],j=c.position+"Field",k=!0,l=[];d.forEach(function(a){-1===i.indexOf(a[j])&&i.push(a[j])},this),i=i.sort(function(a,b){return c._scale(a)-c._scale(b)}),null===h.node()&&(k=!1,h=e._group.append("linearGradient").attr("id",b).attr("gradientUnits","userSpaceOnUse").attr("x1","x"===c.position?c._scale(i[0])+e._widthPixels()/i.length/2:0).attr("y1","y"===c.position?c._scale(i[0])-e._heightPixels()/i.length/2:0).attr("x2","x"===c.position?c._scale(i[i.length-1])+e._widthPixels()/i.length/2:0).attr("y2","y"===c.position?c._scale(i[i.length-1])-e._heightPixels()/i.length/2:0)),i.forEach(function(b,c){var e={},f=0;for(f=0;f=1&&(h.forEach(function(a){var c=null===a.desc||void 0===a.desc?!1:a.desc,d=a.ordering,f=[],g="string"==typeof d?d:null,h=function(a){var b,c=0;for(b=0;bb[0]&&(c=1):c=Date.parse(a[0])-Date.parse(b[0]):c=parseFloat(d)-parseFloat(e),c};"function"==typeof d?e.push(function(a,b){return(c?-1:1)*d(a,b)}):d instanceof Array?(d.forEach(function(a){f.push([].concat(a).join("|"))},this),e.push(function(a,d){var e,g,h="".concat(a[b]),i="".concat(d[b]);return e=f.indexOf(h),g=f.indexOf(i),e=0>e?c?-1:f.length:e,g=0>g?c?-1:f.length:g,(c?-1:1)*(e-g)})):e.push(function(a,b){var d=0;return void 0!==a[g]&&void 0!==b[g]&&(d=i([].concat(a[g]),[].concat(b[g]))),(c?-1:1)*d})}),d.sort(function(a,b){for(var c=0,d=0;c=2?c.x._scale(a.cx)+dimple._helpers.xGap(b,c)+(a.xOffset+.5)*(b._widthPixels()/c.x._max-2*dimple._helpers.xGap(b,c))*a.width:c.x._scale(a.cx)+b._widthPixels()/c.x._max/2},cy:function(a,b,c){var d=0;return d=null!==c.y.measure&&void 0!==c.y.measure?c.y._scale(a.cy):null!==c.y.categoryFields&&void 0!==c.y.categoryFields&&c.y.categoryFields.length>=2?c.y._scale(a.cy)-b._heightPixels()/c.y._max+dimple._helpers.yGap(b,c)+(a.yOffset+.5)*(b._heightPixels()/c.y._max-2*dimple._helpers.yGap(b,c))*a.height:c.y._scale(a.cy)-b._heightPixels()/c.y._max/2},r:function(a,b,c){var d=0;return d=null===c.z||void 0===c.z?5:c.z._hasMeasure()?c.z._scale(a.r):c.z._scale(b._heightPixels()/100)},xGap:function(a,b){var c=0;return(null===b.x.measure||void 0===b.x.measure)&&b.barGap>0&&(c=a._widthPixels()/b.x._max*(b.barGap>.99?.99:b.barGap)/2),c},xClusterGap:function(a,b,c){var d=0;return null!==c.x.categoryFields&&void 0!==c.x.categoryFields&&c.x.categoryFields.length>=2&&c.clusterBarGap>0&&!c.x._hasMeasure()&&(d=a.width*(b._widthPixels()/c.x._max-2*dimple._helpers.xGap(b,c))*(c.clusterBarGap>.99?.99:c.clusterBarGap)/2),d},yGap:function(a,b){var c=0;return(null===b.y.measure||void 0===b.y.measure)&&b.barGap>0&&(c=a._heightPixels()/b.y._max*(b.barGap>.99?.99:b.barGap)/2),c},yClusterGap:function(a,b,c){var d=0;return null!==c.y.categoryFields&&void 0!==c.y.categoryFields&&c.y.categoryFields.length>=2&&c.clusterBarGap>0&&!c.y._hasMeasure()&&(d=a.height*(b._heightPixels()/c.y._max-2*dimple._helpers.yGap(b,c))*(c.clusterBarGap>.99?.99:c.clusterBarGap)/2),d},x:function(a,b,c){var d=0;return d=c.x._hasTimeField()?c.x._scale(a.x)-dimple._helpers.width(a,b,c)/2:null!==c.x.measure&&void 0!==c.x.measure?c.x._scale(a.x):c.x._scale(a.x)+dimple._helpers.xGap(b,c)+a.xOffset*(dimple._helpers.width(a,b,c)+2*dimple._helpers.xClusterGap(a,b,c))+dimple._helpers.xClusterGap(a,b,c)},y:function(a,b,c){var d=0;return d=c.y._hasTimeField()?c.y._scale(a.y)-dimple._helpers.height(a,b,c)/2:null!==c.y.measure&&void 0!==c.y.measure?c.y._scale(a.y):c.y._scale(a.y)-b._heightPixels()/c.y._max+dimple._helpers.yGap(b,c)+a.yOffset*(dimple._helpers.height(a,b,c)+2*dimple._helpers.yClusterGap(a,b,c))+dimple._helpers.yClusterGap(a,b,c)},width:function(a,b,c){var d=0;return d=null!==c.x.measure&&void 0!==c.x.measure?Math.abs(c.x._scale(a.x<0?a.x-a.width:a.x+a.width)-c.x._scale(a.x)):c.x._hasTimeField()?c.x.floatingBarWidth:a.width*(b._widthPixels()/c.x._max-2*dimple._helpers.xGap(b,c))-2*dimple._helpers.xClusterGap(a,b,c)},height:function(a,b,c){var d=0;return d=c.y._hasTimeField()?c.y.floatingBarWidth:null!==c.y.measure&&void 0!==c.y.measure?Math.abs(c.y._scale(a.y)-c.y._scale(a.y<=0?a.y+a.height:a.y-a.height)):a.height*(b._heightPixels()/c.y._max-2*dimple._helpers.yGap(b,c))-2*dimple._helpers.yClusterGap(a,b,c)},opacity:function(a,b,c){var d=0;return d=null!==c.c&&void 0!==c.c?a.opacity:b.getColor(a.aggField.slice(-1)[0]).opacity},fill:function(a,b,c){var d=0;return d=null!==c.c&&void 0!==c.c?a.fill:b.getColor(a.aggField.slice(-1)[0]).fill},stroke:function(a,b,c){var d=0;return d=null!==c.c&&void 0!==c.c?a.stroke:b.getColor(a.aggField.slice(-1)[0]).stroke}},dimple._parentHeight=function(a){var b=a.offsetHeight;return(0>=b||null===b||void 0===b)&&(b=a.clientHeight),(0>=b||null===b||void 0===b)&&(b=null===a.parentNode||void 0===a.parentNode?0:dimple._parseYPosition(d3.select(a).attr("height"),a.parentNode)),b},dimple._parentWidth=function(a){var b=a.offsetWidth;return(0>=b||null===b||void 0===b)&&(b=a.clientWidth),(0>=b||null===b||void 0===b)&&(b=null===a.parentNode||void 0===a.parentNode?0:dimple._parseXPosition(d3.select(a).attr("width"),a.parentNode)),b},dimple._parseXPosition=function(a,b){var c=0,d=a.toString().split(",");return d.forEach(function(d){void 0!==d&&null!==d&&(isNaN(d)?"%"===d.slice(-1)?c+=dimple._parentWidth(b)*(parseFloat(d.slice(0,d.length-1))/100):"px"===d.slice(-2)?c+=parseFloat(d.slice(0,d.length-2)):c=a:c+=parseFloat(d))},this),c},dimple._parseYPosition=function(a,b){var c=0,d=a.toString().split(",");return d.forEach(function(d){void 0!==d&&null!==d&&(isNaN(d)?"%"===d.slice(-1)?c+=dimple._parentHeight(b)*(parseFloat(d.slice(0,d.length-1))/100):"px"===d.slice(-2)?c+=parseFloat(d.slice(0,d.length-2)):c=a:c+=parseFloat(d))},this),c},dimple._rollUp=function(a,b,c){var d=[];return b=null!==b&&void 0!==b?[].concat(b):[],a.forEach(function(a){var e=-1,f={},g=!0;d.forEach(function(c,d){-1===e&&(g=!0,b.forEach(function(b){g=g&&a[b]===c[b]},this),g&&(e=d))},this),-1!==e?f=d[e]:(b.forEach(function(b){f[b]=a[b]},this),d.push(f),e=d.length-1),c.forEach(function(c){-1===b.indexOf(c)&&(void 0===f[c]&&(f[c]=[]),f[c]=f[c].concat(a[c]))},this),d[e]=f},this),d},dimple.filterData=function(a,b,c){var d=a;return null!==b&&null!==c&&(null!==c&&void 0!==c&&(c=[].concat(c)),d=[],a.forEach(function(a){null===a[b]?d.push(a):c.indexOf([].concat(a[b]).join("/"))>-1&&d.push(a)},this)),d},dimple.getUniqueValues=function(a,b){var c=[];return null!==b&&void 0!==b&&(b=[].concat(b),a.forEach(function(a){var d="";b.forEach(function(b,c){c>0&&(d+="/"),d+=a[b]},this),-1===c.indexOf(d)&&c.push(d)},this)),c},dimple.newSvg=function(a,b,c){var d=null;if((null===a||void 0===a)&&(a="body"),d=d3.select(a),d.empty())throw"The '"+a+"' selector did not match any elements. Please prefix with '#' to select by id or '.' to select by class";return d.append("svg").attr("width",b).attr("height",c)}}(); \ No newline at end of file diff --git a/examples/advanced_bar_labels.html b/examples/advanced_bar_labels.html index fbe11c7..2f20662 100644 --- a/examples/advanced_bar_labels.html +++ b/examples/advanced_bar_labels.html @@ -3,7 +3,7 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +