From 6df446185efac246f5ba495bf604e0285f7626bb Mon Sep 17 00:00:00 2001 From: John Kiernander Date: Thu, 12 Sep 2013 10:49:22 +0100 Subject: [PATCH] Update to 1.1.0 --- dist/dimple.v1.0.0.js | 4031 ++++++++++++++++ dist/dimple.v1.0.0.min.js | 2 + dist/dimple.v1.0.1.js | 4031 ++++++++++++++++ dist/dimple.v1.0.1.min.js | 2 + dist/dimple.v1.1.0.js | 4149 +++++++++++++++++ dist/dimple.v1.1.0.min.js | 2 + dist/dimple.v1.js | 4100 ++++++++++++++++ dist/dimple.v1.min.js | 2 + examples/advanced_bar_labels.html | 3 + examples/advanced_bullet.html | 3 + examples/advanced_dynamic_line_color.html | 3 + examples/advanced_grouped_mekko.html | 3 + examples/advanced_interactive_legends.html | 3 + examples/advanced_lollipop_with_hover.html | 3 + examples/advanced_matrix.html | 3 + examples/advanced_pong.html | 3 + examples/advanced_price_range_lollipop.html | 3 + examples/advanced_storyboard_control.html | 3 + examples/advanced_time_axis.html | 3 + examples/advanced_trellis_bar.html | 3 + examples/advanced_waterfall.html | 3 + examples/areas_horizontal.html | 3 + examples/areas_horizontal_grouped.html | 3 + examples/areas_horizontal_grouped_100pct.html | 3 + .../areas_horizontal_grouped_stacked.html | 3 + examples/areas_horizontal_stacked.html | 3 + examples/areas_horizontal_stacked_100pct.html | 3 + examples/areas_vertical.html | 3 + examples/areas_vertical_grouped.html | 3 + examples/areas_vertical_grouped_100pct.html | 3 + examples/areas_vertical_grouped_stacked.html | 3 + examples/areas_vertical_stacked.html | 3 + examples/areas_vertical_stacked_100pct.html | 3 + examples/bars_horizontal.html | 3 + examples/bars_horizontal_grouped.html | 3 + examples/bars_horizontal_grouped_stacked.html | 3 + ...ars_horizontal_grouped_stacked_100pct.html | 3 + examples/bars_horizontal_mekko.html | 3 + examples/bars_horizontal_stacked.html | 3 + examples/bars_horizontal_stacked_100pct.html | 3 + examples/bars_matrix.html | 3 + examples/bars_vertical.html | 3 + examples/bars_vertical_grouped.html | 3 + examples/bars_vertical_grouped_stacked.html | 3 + .../bars_vertical_grouped_stacked_100pct.html | 3 + examples/bars_vertical_mekko.html | 3 + examples/bars_vertical_stacked.html | 3 + examples/bars_vertical_stacked_100pct.html | 3 + examples/bubbles_horizontal_grouped.html | 3 + examples/bubbles_horizontal_lollipop.html | 3 + examples/bubbles_matrix.html | 3 + examples/bubbles_standard.html | 3 + examples/bubbles_vertical_grouped.html | 3 + examples/bubbles_vertical_lollipop.html | 3 + examples/lines_horizontal.html | 3 + examples/lines_horizontal_grouped.html | 3 + .../lines_horizontal_grouped_stacked.html | 3 + examples/lines_horizontal_stacked.html | 3 + examples/lines_vertical.html | 3 + examples/lines_vertical_grouped.html | 3 + examples/lines_vertical_grouped_stacked.html | 3 + examples/lines_vertical_stacked.html | 3 + examples/scatter_horizontal_grouped.html | 3 + examples/scatter_horizontal_lollipop.html | 3 + examples/scatter_matrix.html | 3 + examples/scatter_standard.html | 3 + examples/scatter_vertical_grouped.html | 3 + examples/scatter_vertical_lollipop.html | 3 + images/advanced_bar_labels.png | Bin 13316 -> 13316 bytes images/advanced_bullet.png | Bin 12045 -> 12045 bytes images/advanced_dynamic_line_color.png | Bin 13575 -> 13575 bytes images/advanced_grouped_mekko.png | Bin 7942 -> 7942 bytes images/advanced_interactive_legends.png | Bin 10514 -> 10514 bytes images/advanced_lollipop_with_hover.png | Bin 7569 -> 7569 bytes images/advanced_matrix.png | Bin 5987 -> 5987 bytes images/advanced_pong.png | Bin 573 -> 573 bytes images/advanced_price_range_lollipop.png | Bin 11113 -> 11113 bytes images/advanced_storyboard_control.png | Bin 13723 -> 13723 bytes images/advanced_time_axis.png | Bin 12894 -> 12894 bytes images/advanced_trellis_bar.png | Bin 20416 -> 20416 bytes images/advanced_waterfall.png | Bin 6391 -> 6391 bytes images/areas_horizontal.png | Bin 10056 -> 10056 bytes images/areas_horizontal_grouped.png | Bin 9569 -> 9569 bytes images/areas_horizontal_grouped_100pct.png | Bin 23482 -> 23482 bytes images/areas_horizontal_grouped_stacked.png | Bin 20887 -> 20887 bytes images/areas_horizontal_stacked.png | Bin 13130 -> 13130 bytes images/areas_horizontal_stacked_100pct.png | Bin 10454 -> 10454 bytes images/areas_vertical.png | Bin 9419 -> 9419 bytes images/areas_vertical_grouped.png | Bin 8934 -> 8934 bytes images/areas_vertical_grouped_100pct.png | Bin 21706 -> 21706 bytes images/areas_vertical_grouped_stacked.png | Bin 19139 -> 19139 bytes images/areas_vertical_stacked.png | Bin 12475 -> 12475 bytes images/areas_vertical_stacked_100pct.png | Bin 9633 -> 9633 bytes images/bars_horizontal.png | Bin 10145 -> 10145 bytes images/bars_horizontal_grouped.png | Bin 5057 -> 5057 bytes images/bars_horizontal_grouped_stacked.png | Bin 6903 -> 6903 bytes ...bars_horizontal_grouped_stacked_100pct.png | Bin 9113 -> 9113 bytes images/bars_horizontal_mekko.png | Bin 7327 -> 7327 bytes images/bars_horizontal_stacked.png | Bin 11792 -> 11792 bytes images/bars_horizontal_stacked_100pct.png | Bin 11101 -> 11101 bytes images/bars_matrix.png | Bin 7709 -> 7709 bytes images/bars_vertical.png | Bin 12709 -> 12709 bytes images/bars_vertical_grouped.png | Bin 4989 -> 4989 bytes images/bars_vertical_grouped_stacked.png | Bin 6885 -> 6885 bytes .../bars_vertical_grouped_stacked_100pct.png | Bin 10210 -> 10210 bytes images/bars_vertical_mekko.png | Bin 6579 -> 6579 bytes images/bars_vertical_stacked.png | Bin 14504 -> 14504 bytes images/bars_vertical_stacked_100pct.png | Bin 16228 -> 16228 bytes images/bubbles_horizontal_grouped.png | Bin 5203 -> 5203 bytes images/bubbles_horizontal_lollipop.png | Bin 7775 -> 7775 bytes images/bubbles_matrix.png | Bin 10043 -> 10043 bytes images/bubbles_standard.png | Bin 9276 -> 9276 bytes images/bubbles_vertical_grouped.png | Bin 5153 -> 5153 bytes images/bubbles_vertical_lollipop.png | Bin 7464 -> 7464 bytes images/lines_horizontal.png | Bin 9776 -> 9776 bytes images/lines_horizontal_grouped.png | Bin 9405 -> 9405 bytes images/lines_horizontal_grouped_stacked.png | Bin 11991 -> 11991 bytes images/lines_horizontal_stacked.png | Bin 12562 -> 12562 bytes images/lines_vertical.png | Bin 9387 -> 9387 bytes images/lines_vertical_grouped.png | Bin 8973 -> 8973 bytes images/lines_vertical_grouped_stacked.png | Bin 11681 -> 11681 bytes images/lines_vertical_stacked.png | Bin 12436 -> 12436 bytes images/scatter_horizontal_grouped.png | Bin 4248 -> 4248 bytes images/scatter_horizontal_lollipop.png | Bin 9618 -> 9618 bytes images/scatter_matrix.png | Bin 6615 -> 6615 bytes images/scatter_standard.png | Bin 6613 -> 6613 bytes images/scatter_vertical_grouped.png | Bin 4227 -> 4227 bytes images/scatter_vertical_lollipop.png | Bin 10218 -> 10218 bytes 128 files changed, 16499 insertions(+) create mode 100644 dist/dimple.v1.0.0.js create mode 100644 dist/dimple.v1.0.0.min.js create mode 100644 dist/dimple.v1.0.1.js create mode 100644 dist/dimple.v1.0.1.min.js create mode 100644 dist/dimple.v1.1.0.js create mode 100644 dist/dimple.v1.1.0.min.js create mode 100644 dist/dimple.v1.js create mode 100644 dist/dimple.v1.min.js diff --git a/dist/dimple.v1.0.0.js b/dist/dimple.v1.0.0.js new file mode 100644 index 0000000..f0c6cff --- /dev/null +++ b/dist/dimple.v1.0.0.js @@ -0,0 +1,4031 @@ +// 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.0.0", + 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) { + // 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.x, this.chart.x + this.chart.width]) + .domain([this._min, this._max]); + } else if (this.useLog) { + this._scale = d3.scale.log() + .range([this.chart.x, this.chart.x + this.chart.width]) + .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.x, this.chart.x + this.chart.width]) + .domain(distinctCats.concat([""])); + } else { + this._scale = d3.scale.linear() + .range([this.chart.x, this.chart.x + this.chart.width]) + .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.y + this.chart.height, this.chart.y]) + .domain([this._min, this._max]); + } else if (this.useLog) { + this._scale = d3.scale.log() + .range([this.chart.y + this.chart.height, this.chart.y]) + .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.y + this.chart.height, this.chart.y]) + .domain(distinctCats.concat([""])); + } else { + this._scale = d3.scale.linear() + .range([this.chart.y + this.chart.height, this.chart.y]) + .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.height / 300, this.chart.height / 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.height / 300, this.chart.height / 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 + 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 = svg.node().offsetWidth * 0.1; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-y + this.y = svg.node().offsetHeight * 0.1; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-width + this.width = svg.node().offsetWidth * 0.8; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-height + this.height = svg.node().offsetHeight * 0.8; + // 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/_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/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"), + 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") + ]; + // 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) { + // 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; + // Many of the draw methods use positioning data in each series. Therefore we should + // decorate the series with it now + 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) : this.y + this.height) + ")"; + gridTransform = "translate(0, " + (axis === firstX ? this.y + this.height : this.y) + ")"; + gridSize = -this.height; + } else if (axis === firstY && firstX !== null) { + transform = "translate(" + (firstX.categoryFields === null || firstX.categoryFields.length === 0 ? firstX._scale(0) : this.x) + ", 0)"; + gridTransform = "translate(" + (axis === firstY ? this.x : this.x + this.width) + ", 0)"; + gridSize = -this.width; + } else if (axis.position === "x") { + gridTransform = transform = "translate(0, " + (axis === firstX ? this.y + this.height : this.y) + ")"; + gridSize = -this.height; + } else if (axis.position === "y") { + gridTransform = transform = "translate(" + (axis === firstY ? this.x : this.x + this.width) + ", 0)"; + gridSize = -this.width; + } + // 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(".axis text")).attr("x", (this.width / axis._max) / 2); + } else if (axis.position === "y") { + handleTrans(axis.shapes.selectAll(".axis text")).attr("y", -1 * (this.height / 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(".axis text")).attr("y", this.y + this.height - firstY._scale(0) + 9); + } + if (axis === firstY && (firstX.categoryFields === null || firstX.categoryFields.length === 0)) { + handleTrans(axis.shapes.selectAll(".axis text")).attr("x", -1 * (firstX._scale(0) - this.x) - 9); + } + } + } + // Set some initial css values + if (!this.noFormats) { + handleTrans(axis.shapes.selectAll(".axis text")) + .style("font-family", "sans-serif") + .style("font-size", (this.height / 35 > 10 ? this.height / 35 : 10) + "px"); + handleTrans(axis.shapes.selectAll(".axis path, .axis line")) + .style("fill", "none") + .style("stroke", "black") + .style("shape-rendering", "crispEdges"); + if (axis.gridlineShapes !== null) { + handleTrans(axis.gridlineShapes.selectAll(".gridlines 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(".axis text").each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > this.width / axis.shapes.selectAll(".axis text")[0].length) { + rotated = true; + axis.shapes.selectAll(".axis 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 if (axis.position === "x") { + // If the gaps are narrower than the widest label display all labels horizontally + widest = 0; + axis.shapes.selectAll(".axis text") + .each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > this.width / axis.shapes.selectAll(".axis text")[0].length) { + rotated = true; + axis.shapes.selectAll(".axis 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)"); + }); + } + } + } + if (axis.titleShape === null && axis.shapes !== null && axis.shapes.node().firstChild !== null) { + // Get the bounds of the axis objects + axis.shapes.selectAll(".axis 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 = this.y + this.height + box.b + 5; + } else { + titleY = this.y + box.t - 10; + } + titleX = this.x + (this.width / 2); + } else if (axis.position === "y") { + if (axis === firstY) { + titleX = this.x + box.l - 10; + } else { + titleX = this.x + this.width + box.r + 20; + } + titleY = this.y + (this.height / 2); + rotate = "rotate(270, " + titleX + ", " + titleY + ")"; + } + + // Add a title for the axis + 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", (chart.height / 35 > 10 ? chart.height / 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) { + // Handle non-integer size expressions + this.x = dimple._parsePosition(x, this.svg.node().offsetWidth); + this.y = dimple._parsePosition(y, this.svg.node().offsetHeight); + this.width = dimple._parsePosition(width, this.svg.node().offsetWidth); + this.height = dimple._parsePosition(height, this.svg.node().offsetHeight); + // Refresh the axes to redraw them against the new bounds + this.draw(); + // 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 = dimple._parsePosition(x, this.chart.svg.node().offsetWidth); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-y + this.y = dimple._parsePosition(y, this.chart.svg.node().offsetHeight); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-width + this.width = dimple._parsePosition(width, this.chart.svg.node().offsetWidth); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-height + this.height = dimple._parsePosition(height, this.chart.svg.node().offsetHeight); + // 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; + // Source: /src/objects/legend/methods/_draw.js + 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.height / 35 > 10 ? self.chart.height / 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.width) { + runningX = 0; + runningY += maxHeight; + } + if (runningY > self.height) { + d3.select(this).remove(); + } else { + d3.select(this).select("text") + .attr("x", (self.horizontalAlign === "left" ? self.x + keyWidth + 5 + runningX : self.x + (self.width - 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.y + runningY + this.getBBox().height / 1.65; + }) + .attr("width", self.width) + .attr("height", self.height); + d3.select(this).select("rect") + .attr("class", "legend legendKey") + .attr("x", (self.horizontalAlign === "left" ? self.x + runningX : self.x + (self.width - runningX - maxWidth))) + .attr("y", self.y + 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; + }; + + // Source: /src/objects/legend/methods/_getEntries.js + 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; + }; + + }; + // 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.x; + } else { + firstOrig.x = axis._origin; + } + } else if (axis.position === "y" && firstOrig.y === null) { + if (axis._hasTimeField()) { + firstOrig.y = this.chart.y + this.chart.height; + } 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.y; + } + } + 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.x + this.chart.width; + } + } + 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, + 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.x + this.chart.width * 0.01) + .attr("y", this.chart.y + (this.chart.height / 35 > 10 ? this.chart.height / 35 : 10) * (xCount > 1 ? 1.25 : -1)) + .call(function () { + if (!chart.noFormats) { + this.style("font-family", "sans-serif") + .style("font-size", (chart.height / 35 > 10 ? chart.height / 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.width / cats.length) / 2) : 0)) + .attr("y1", (categoryAxis.position === "y" ? categoryAxis._scale(cats[0]) - ((chart.height / cats.length) / 2) : 0)) + .attr("x2", (categoryAxis.position === "x" ? categoryAxis._scale(cats[cats.length - 1]) + ((chart.width / cats.length) / 2) : 0)) + .attr("y2", (categoryAxis.position === "y" ? categoryAxis._scale(cats[cats.length - 1]) - ((chart.height / 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.width / series.x._max) - 2 * dimple._helpers.xGap(chart, series)) * d.width)); + } else { + returnCx = series.x._scale(d.cx) + ((chart.width / 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.height / series.y._max)) + dimple._helpers.yGap(chart, series) + ((d.yOffset + 0.5) * (((chart.height / series.y._max) - 2 * dimple._helpers.yGap(chart, series)) * d.height)); + } else { + returnCy = series.y._scale(d.cy) - ((chart.height / 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.height / 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.width / 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.width / 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.height / 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.height / 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.height / 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.width / 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.height / 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/_parsePosition.js + dimple._parsePosition = function (value, svgScaleValue) { + var returnValue = value; + if (!isNaN(value)) { + returnValue = value; + } else if (value.slice(-1) === "%") { + returnValue = svgScaleValue * (parseFloat(value.slice(0, value.length - 1)) / 100); + } else if (value.slice(-2) === "px") { + returnValue = parseFloat(value.slice(0, value.length - 2)); + } + 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.0.0.min.js b/dist/dimple.v1.0.0.min.js new file mode 100644 index 0000000..4ae8bec --- /dev/null +++ b/dist/dimple.v1.0.0.min.js @@ -0,0 +1,2 @@ +var dimple={version:"1.0.0",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?Date.parse(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.x,this.chart.x+this.chart.width]).domain([this._min,this._max]):this.useLog?this._scale=d3.scale.log().range([this.chart.x,this.chart.x+this.chart.width]).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.x,this.chart.x+this.chart.width]).domain(f.concat([""]))):this._scale=d3.scale.linear().range([this.chart.x,this.chart.x+this.chart.width]).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.y+this.chart.height,this.chart.y]).domain([this._min,this._max]):this.useLog?this._scale=d3.scale.log().range([this.chart.y+this.chart.height,this.chart.y]).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.y+this.chart.height,this.chart.y]).domain(f.concat([""]))):this._scale=d3.scale.linear().range([this.chart.y+this.chart.height,this.chart.y]).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.height/300,this.chart.height/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.height/300,this.chart.height/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=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=.1*a.node().offsetWidth,this.y=.1*a.node().offsetHeight,this.width=.8*a.node().offsetWidth,this.height=.8*a.node().offsetHeight,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._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.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){a=null===a||void 0===a?0:a;var b,c=null,d=null,e=!1,f=!1;return this._getSeriesData(),this.axes.forEach(function(a){if(a._min=0,a._max=0,a._hasMeasure()){var e=!1;this.series.forEach(function(b){if(b[a.position]===a){var c=b._axisBounds(a.position);a._min>c.min&&(a._min=c.min),a._maxb[a.measure]&&(a._min=b[a.measure]),a._maxa._max)&&(a._max=c)},this)):a._hasCategories()&&(a._min=0,b=[],this.data.forEach(function(c){-1===b.indexOf(c[a.categoryFields[0]])&&b.push(c[a.categoryFields[0]])},this),a._max=b.length);a._update(),null===c&&"x"===a.position?c=a:null===d&&"y"===a.position&&(d=a)},this),this.axes.forEach(function(b){var g,h=!1,i=null,j=0,k=null,l=!1,m=0,n={l:null,t:null,r:null,b:null},o=0,p=0,q="",r=this;null===b.gridlineShapes?(b.showGridlines||null===b.showGridlines&&!b._hasCategories()&&(!e&&"x"===b.position||!f&&"y"===b.position))&&(b.gridlineShapes=this._group.append("g").attr("class","gridlines"),"x"===b.position?e=!0:f=!0):"x"===b.position?e=!0:f=!0,null===b.shapes&&(b.shapes=this._group.append("g").attr("class","axis"),h=!0),b===c&&null!==d?(i="translate(0, "+(null===d.categoryFields||0===d.categoryFields.length?d._scale(0):this.y+this.height)+")",k="translate(0, "+(b===c?this.y+this.height:this.y)+")",j=-this.height):b===d&&null!==c?(i="translate("+(null===c.categoryFields||0===c.categoryFields.length?c._scale(0):this.x)+", 0)",k="translate("+(b===d?this.x:this.x+this.width)+", 0)",j=-this.width):"x"===b.position?(k=i="translate(0, "+(b===c?this.y+this.height:this.y)+")",j=-this.height):"y"===b.position&&(k=i="translate("+(b===d?this.x:this.x+this.width)+", 0)",j=-this.width),g=function(b){var c;return c=null===i||0===a||h?b:b.transition().duration(a)},null!==i&&null!==b._draw&&(b._hasTimeField()?g(b.shapes).call(b._draw.ticks(b._getTimePeriod(),b.timeInterval).tickFormat(b._getFormat())).attr("transform",i):b.useLog?g(b.shapes).call(b._draw.ticks(4,b._getFormat())).attr("transform",i):g(b.shapes).call(b._draw.tickFormat(b._getFormat())).attr("transform",i),null!==b.gridlineShapes&&g(b.gridlineShapes).call(b._draw.tickSize(j,0,0).tickFormat("")).attr("transform",k),(null===b.measure||void 0===b.measure)&&("x"===b.position?g(b.shapes.selectAll(".axis text")).attr("x",this.width/b._max/2):"y"===b.position&&g(b.shapes.selectAll(".axis text")).attr("y",-1*(this.height/b._max)/2)),null!==b.categoryFields&&void 0!==b.categoryFields&&b.categoryFields.length>0&&(b!==c||null!==d.categoryFields&&0!==d.categoryFields.length||g(b.shapes.selectAll(".axis text")).attr("y",this.y+this.height-d._scale(0)+9),b!==d||null!==c.categoryFields&&0!==c.categoryFields.length||g(b.shapes.selectAll(".axis text")).attr("x",-1*(c._scale(0)-this.x)-9))),this.noFormats||(g(b.shapes.selectAll(".axis text")).style("font-family","sans-serif").style("font-size",(this.height/35>10?this.height/35:10)+"px"),g(b.shapes.selectAll(".axis path, .axis line")).style("fill","none").style("stroke","black").style("shape-rendering","crispEdges"),null!==b.gridlineShapes&&g(b.gridlineShapes.selectAll(".gridlines line")).style("fill","none").style("stroke","lightgray").style("opacity",.8)),(null===b.measure||void 0===b.measure)&&(b===c?(m=0,b.shapes.selectAll(".axis text").each(function(){var a=this.getComputedTextLength();m=a>m?a:m}),m>this.width/b.shapes.selectAll(".axis text")[0].length&&(l=!0,b.shapes.selectAll(".axis 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)")}))):"x"===b.position&&(m=0,b.shapes.selectAll(".axis text").each(function(){var a=this.getComputedTextLength();m=a>m?a:m}),m>this.width/b.shapes.selectAll(".axis text")[0].length&&(l=!0,b.shapes.selectAll(".axis 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)")})))),null===b.titleShape&&null!==b.shapes&&null!==b.shapes.node().firstChild&&(b.shapes.selectAll(".axis text").each(function(){var a=this.getBBox();(null===n.l||-9-a.widthn.r)&&(n.r=a.x+a.width),l?((null===n.t||a.y+a.height-a.widthn.b)&&(n.b=a.height+a.width)):((null===n.t||a.yn.b)&&(n.b=9+a.height))}),"x"===b.position?(p=b===c?this.y+this.height+n.b+5:this.y+n.t-10,o=this.x+this.width/2):"y"===b.position&&(o=b===d?this.x+n.l-10:this.x+this.width+n.r+20,p=this.y+this.height/2,q="rotate(270, "+o+", "+p+")"),b.titleShape=this._group.append("text").attr("class","axis title"),b.titleShape.attr("x",o).attr("y",p).attr("text-anchor","middle").attr("transform",q).text(null===b.categoryFields||void 0===b.categoryFields||0===b.categoryFields.length?b.measure:b.categoryFields.join("/")).each(function(){r.noFormats||d3.select(this).style("font-family","sans-serif").style("font-size",(r.height/35>10?r.height/35:10)+"px")}),b===c?b.titleShape.each(function(){d3.select(this).attr("y",p+this.getBBox().height/1.65)}):b===d&&b.titleShape.each(function(){d3.select(this).attr("x",o+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=dimple._parsePosition(a,this.svg.node().offsetWidth),this.y=dimple._parsePosition(b,this.svg.node().offsetHeight),this.width=dimple._parsePosition(c,this.svg.node().offsetWidth),this.height=dimple._parsePosition(d,this.svg.node().offsetHeight),this.draw(),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=dimple._parsePosition(b,this.chart.svg.node().offsetWidth),this.y=dimple._parsePosition(c,this.chart.svg.node().offsetHeight),this.width=dimple._parsePosition(d,this.chart.svg.node().offsetWidth),this.height=dimple._parsePosition(e,this.chart.svg.node().offsetHeight),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.height/35>10?j.chart.height/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.width&&(f=0,g+=e),g>j.height?d3.select(this).remove():(d3.select(this).select("text").attr("x","left"===j.horizontalAlign?j.x+h+5+f:j.x+(j.width-f-d)+h+5).attr("y",function(){return j.y+g+this.getBBox().height/1.65}).attr("width",j.width).attr("height",j.height),d3.select(this).select("rect").attr("class","legend legendKey").attr("x","left"===j.horizontalAlign?j.x+f:j.x+(j.width-f-d)).attr("y",j.y+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.x:a._origin:"y"===a.position&&null===d.y&&(d.y=a._hasTimeField()?this.chart.y+this.chart.height: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.x+this.chart.width)),b+=1):(e===this.x&&(0===a?c.y=d.y:1===a&&(c.y=this.chart.y)),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,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.x+.01*this.chart.width).attr("y",this.chart.y+(this.chart.height/35>10?this.chart.height/35:10)*(c>1?1.25:-1)).call(function(){b.noFormats||this.style("font-family","sans-serif").style("font-size",(b.height/35>10?b.height/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.width/i.length/2:0).attr("y1","y"===c.position?c._scale(i[0])-e.height/i.length/2:0).attr("x2","x"===c.position?c._scale(i[i.length-1])+e.width/i.length/2:0).attr("y2","y"===c.position?c._scale(i[i.length-1])-e.height/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.width/c.x._max-2*dimple._helpers.xGap(b,c))*a.width:c.x._scale(a.cx)+b.width/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.height/c.y._max+dimple._helpers.yGap(b,c)+(a.yOffset+.5)*(b.height/c.y._max-2*dimple._helpers.yGap(b,c))*a.height:c.y._scale(a.cy)-b.height/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.height/100)},xGap:function(a,b){var c=0;return(null===b.x.measure||void 0===b.x.measure)&&b.barGap>0&&(c=a.width/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.width/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.height/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.height/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.height/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.width/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.height/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._parsePosition=function(a,b){var c=a;return isNaN(a)?"%"===a.slice(-1)?c=b*(parseFloat(a.slice(0,a.length-1))/100):"px"===a.slice(-2)&&(c=parseFloat(a.slice(0,a.length-2))):c=a,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/dist/dimple.v1.0.1.js b/dist/dimple.v1.0.1.js new file mode 100644 index 0000000..f0c6cff --- /dev/null +++ b/dist/dimple.v1.0.1.js @@ -0,0 +1,4031 @@ +// 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.0.0", + 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) { + // 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.x, this.chart.x + this.chart.width]) + .domain([this._min, this._max]); + } else if (this.useLog) { + this._scale = d3.scale.log() + .range([this.chart.x, this.chart.x + this.chart.width]) + .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.x, this.chart.x + this.chart.width]) + .domain(distinctCats.concat([""])); + } else { + this._scale = d3.scale.linear() + .range([this.chart.x, this.chart.x + this.chart.width]) + .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.y + this.chart.height, this.chart.y]) + .domain([this._min, this._max]); + } else if (this.useLog) { + this._scale = d3.scale.log() + .range([this.chart.y + this.chart.height, this.chart.y]) + .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.y + this.chart.height, this.chart.y]) + .domain(distinctCats.concat([""])); + } else { + this._scale = d3.scale.linear() + .range([this.chart.y + this.chart.height, this.chart.y]) + .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.height / 300, this.chart.height / 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.height / 300, this.chart.height / 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 + 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 = svg.node().offsetWidth * 0.1; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-y + this.y = svg.node().offsetHeight * 0.1; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-width + this.width = svg.node().offsetWidth * 0.8; + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-height + this.height = svg.node().offsetHeight * 0.8; + // 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/_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/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"), + 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") + ]; + // 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) { + // 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; + // Many of the draw methods use positioning data in each series. Therefore we should + // decorate the series with it now + 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) : this.y + this.height) + ")"; + gridTransform = "translate(0, " + (axis === firstX ? this.y + this.height : this.y) + ")"; + gridSize = -this.height; + } else if (axis === firstY && firstX !== null) { + transform = "translate(" + (firstX.categoryFields === null || firstX.categoryFields.length === 0 ? firstX._scale(0) : this.x) + ", 0)"; + gridTransform = "translate(" + (axis === firstY ? this.x : this.x + this.width) + ", 0)"; + gridSize = -this.width; + } else if (axis.position === "x") { + gridTransform = transform = "translate(0, " + (axis === firstX ? this.y + this.height : this.y) + ")"; + gridSize = -this.height; + } else if (axis.position === "y") { + gridTransform = transform = "translate(" + (axis === firstY ? this.x : this.x + this.width) + ", 0)"; + gridSize = -this.width; + } + // 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(".axis text")).attr("x", (this.width / axis._max) / 2); + } else if (axis.position === "y") { + handleTrans(axis.shapes.selectAll(".axis text")).attr("y", -1 * (this.height / 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(".axis text")).attr("y", this.y + this.height - firstY._scale(0) + 9); + } + if (axis === firstY && (firstX.categoryFields === null || firstX.categoryFields.length === 0)) { + handleTrans(axis.shapes.selectAll(".axis text")).attr("x", -1 * (firstX._scale(0) - this.x) - 9); + } + } + } + // Set some initial css values + if (!this.noFormats) { + handleTrans(axis.shapes.selectAll(".axis text")) + .style("font-family", "sans-serif") + .style("font-size", (this.height / 35 > 10 ? this.height / 35 : 10) + "px"); + handleTrans(axis.shapes.selectAll(".axis path, .axis line")) + .style("fill", "none") + .style("stroke", "black") + .style("shape-rendering", "crispEdges"); + if (axis.gridlineShapes !== null) { + handleTrans(axis.gridlineShapes.selectAll(".gridlines 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(".axis text").each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > this.width / axis.shapes.selectAll(".axis text")[0].length) { + rotated = true; + axis.shapes.selectAll(".axis 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 if (axis.position === "x") { + // If the gaps are narrower than the widest label display all labels horizontally + widest = 0; + axis.shapes.selectAll(".axis text") + .each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > this.width / axis.shapes.selectAll(".axis text")[0].length) { + rotated = true; + axis.shapes.selectAll(".axis 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)"); + }); + } + } + } + if (axis.titleShape === null && axis.shapes !== null && axis.shapes.node().firstChild !== null) { + // Get the bounds of the axis objects + axis.shapes.selectAll(".axis 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 = this.y + this.height + box.b + 5; + } else { + titleY = this.y + box.t - 10; + } + titleX = this.x + (this.width / 2); + } else if (axis.position === "y") { + if (axis === firstY) { + titleX = this.x + box.l - 10; + } else { + titleX = this.x + this.width + box.r + 20; + } + titleY = this.y + (this.height / 2); + rotate = "rotate(270, " + titleX + ", " + titleY + ")"; + } + + // Add a title for the axis + 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", (chart.height / 35 > 10 ? chart.height / 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) { + // Handle non-integer size expressions + this.x = dimple._parsePosition(x, this.svg.node().offsetWidth); + this.y = dimple._parsePosition(y, this.svg.node().offsetHeight); + this.width = dimple._parsePosition(width, this.svg.node().offsetWidth); + this.height = dimple._parsePosition(height, this.svg.node().offsetHeight); + // Refresh the axes to redraw them against the new bounds + this.draw(); + // 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 = dimple._parsePosition(x, this.chart.svg.node().offsetWidth); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-y + this.y = dimple._parsePosition(y, this.chart.svg.node().offsetHeight); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-width + this.width = dimple._parsePosition(width, this.chart.svg.node().offsetWidth); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-height + this.height = dimple._parsePosition(height, this.chart.svg.node().offsetHeight); + // 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; + // Source: /src/objects/legend/methods/_draw.js + 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.height / 35 > 10 ? self.chart.height / 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.width) { + runningX = 0; + runningY += maxHeight; + } + if (runningY > self.height) { + d3.select(this).remove(); + } else { + d3.select(this).select("text") + .attr("x", (self.horizontalAlign === "left" ? self.x + keyWidth + 5 + runningX : self.x + (self.width - 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.y + runningY + this.getBBox().height / 1.65; + }) + .attr("width", self.width) + .attr("height", self.height); + d3.select(this).select("rect") + .attr("class", "legend legendKey") + .attr("x", (self.horizontalAlign === "left" ? self.x + runningX : self.x + (self.width - runningX - maxWidth))) + .attr("y", self.y + 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; + }; + + // Source: /src/objects/legend/methods/_getEntries.js + 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; + }; + + }; + // 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.x; + } else { + firstOrig.x = axis._origin; + } + } else if (axis.position === "y" && firstOrig.y === null) { + if (axis._hasTimeField()) { + firstOrig.y = this.chart.y + this.chart.height; + } 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.y; + } + } + 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.x + this.chart.width; + } + } + 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, + 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.x + this.chart.width * 0.01) + .attr("y", this.chart.y + (this.chart.height / 35 > 10 ? this.chart.height / 35 : 10) * (xCount > 1 ? 1.25 : -1)) + .call(function () { + if (!chart.noFormats) { + this.style("font-family", "sans-serif") + .style("font-size", (chart.height / 35 > 10 ? chart.height / 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.width / cats.length) / 2) : 0)) + .attr("y1", (categoryAxis.position === "y" ? categoryAxis._scale(cats[0]) - ((chart.height / cats.length) / 2) : 0)) + .attr("x2", (categoryAxis.position === "x" ? categoryAxis._scale(cats[cats.length - 1]) + ((chart.width / cats.length) / 2) : 0)) + .attr("y2", (categoryAxis.position === "y" ? categoryAxis._scale(cats[cats.length - 1]) - ((chart.height / 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.width / series.x._max) - 2 * dimple._helpers.xGap(chart, series)) * d.width)); + } else { + returnCx = series.x._scale(d.cx) + ((chart.width / 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.height / series.y._max)) + dimple._helpers.yGap(chart, series) + ((d.yOffset + 0.5) * (((chart.height / series.y._max) - 2 * dimple._helpers.yGap(chart, series)) * d.height)); + } else { + returnCy = series.y._scale(d.cy) - ((chart.height / 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.height / 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.width / 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.width / 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.height / 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.height / 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.height / 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.width / 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.height / 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/_parsePosition.js + dimple._parsePosition = function (value, svgScaleValue) { + var returnValue = value; + if (!isNaN(value)) { + returnValue = value; + } else if (value.slice(-1) === "%") { + returnValue = svgScaleValue * (parseFloat(value.slice(0, value.length - 1)) / 100); + } else if (value.slice(-2) === "px") { + returnValue = parseFloat(value.slice(0, value.length - 2)); + } + 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.0.1.min.js b/dist/dimple.v1.0.1.min.js new file mode 100644 index 0000000..4ae8bec --- /dev/null +++ b/dist/dimple.v1.0.1.min.js @@ -0,0 +1,2 @@ +var dimple={version:"1.0.0",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?Date.parse(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.x,this.chart.x+this.chart.width]).domain([this._min,this._max]):this.useLog?this._scale=d3.scale.log().range([this.chart.x,this.chart.x+this.chart.width]).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.x,this.chart.x+this.chart.width]).domain(f.concat([""]))):this._scale=d3.scale.linear().range([this.chart.x,this.chart.x+this.chart.width]).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.y+this.chart.height,this.chart.y]).domain([this._min,this._max]):this.useLog?this._scale=d3.scale.log().range([this.chart.y+this.chart.height,this.chart.y]).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.y+this.chart.height,this.chart.y]).domain(f.concat([""]))):this._scale=d3.scale.linear().range([this.chart.y+this.chart.height,this.chart.y]).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.height/300,this.chart.height/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.height/300,this.chart.height/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=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=.1*a.node().offsetWidth,this.y=.1*a.node().offsetHeight,this.width=.8*a.node().offsetWidth,this.height=.8*a.node().offsetHeight,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._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.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){a=null===a||void 0===a?0:a;var b,c=null,d=null,e=!1,f=!1;return this._getSeriesData(),this.axes.forEach(function(a){if(a._min=0,a._max=0,a._hasMeasure()){var e=!1;this.series.forEach(function(b){if(b[a.position]===a){var c=b._axisBounds(a.position);a._min>c.min&&(a._min=c.min),a._maxb[a.measure]&&(a._min=b[a.measure]),a._maxa._max)&&(a._max=c)},this)):a._hasCategories()&&(a._min=0,b=[],this.data.forEach(function(c){-1===b.indexOf(c[a.categoryFields[0]])&&b.push(c[a.categoryFields[0]])},this),a._max=b.length);a._update(),null===c&&"x"===a.position?c=a:null===d&&"y"===a.position&&(d=a)},this),this.axes.forEach(function(b){var g,h=!1,i=null,j=0,k=null,l=!1,m=0,n={l:null,t:null,r:null,b:null},o=0,p=0,q="",r=this;null===b.gridlineShapes?(b.showGridlines||null===b.showGridlines&&!b._hasCategories()&&(!e&&"x"===b.position||!f&&"y"===b.position))&&(b.gridlineShapes=this._group.append("g").attr("class","gridlines"),"x"===b.position?e=!0:f=!0):"x"===b.position?e=!0:f=!0,null===b.shapes&&(b.shapes=this._group.append("g").attr("class","axis"),h=!0),b===c&&null!==d?(i="translate(0, "+(null===d.categoryFields||0===d.categoryFields.length?d._scale(0):this.y+this.height)+")",k="translate(0, "+(b===c?this.y+this.height:this.y)+")",j=-this.height):b===d&&null!==c?(i="translate("+(null===c.categoryFields||0===c.categoryFields.length?c._scale(0):this.x)+", 0)",k="translate("+(b===d?this.x:this.x+this.width)+", 0)",j=-this.width):"x"===b.position?(k=i="translate(0, "+(b===c?this.y+this.height:this.y)+")",j=-this.height):"y"===b.position&&(k=i="translate("+(b===d?this.x:this.x+this.width)+", 0)",j=-this.width),g=function(b){var c;return c=null===i||0===a||h?b:b.transition().duration(a)},null!==i&&null!==b._draw&&(b._hasTimeField()?g(b.shapes).call(b._draw.ticks(b._getTimePeriod(),b.timeInterval).tickFormat(b._getFormat())).attr("transform",i):b.useLog?g(b.shapes).call(b._draw.ticks(4,b._getFormat())).attr("transform",i):g(b.shapes).call(b._draw.tickFormat(b._getFormat())).attr("transform",i),null!==b.gridlineShapes&&g(b.gridlineShapes).call(b._draw.tickSize(j,0,0).tickFormat("")).attr("transform",k),(null===b.measure||void 0===b.measure)&&("x"===b.position?g(b.shapes.selectAll(".axis text")).attr("x",this.width/b._max/2):"y"===b.position&&g(b.shapes.selectAll(".axis text")).attr("y",-1*(this.height/b._max)/2)),null!==b.categoryFields&&void 0!==b.categoryFields&&b.categoryFields.length>0&&(b!==c||null!==d.categoryFields&&0!==d.categoryFields.length||g(b.shapes.selectAll(".axis text")).attr("y",this.y+this.height-d._scale(0)+9),b!==d||null!==c.categoryFields&&0!==c.categoryFields.length||g(b.shapes.selectAll(".axis text")).attr("x",-1*(c._scale(0)-this.x)-9))),this.noFormats||(g(b.shapes.selectAll(".axis text")).style("font-family","sans-serif").style("font-size",(this.height/35>10?this.height/35:10)+"px"),g(b.shapes.selectAll(".axis path, .axis line")).style("fill","none").style("stroke","black").style("shape-rendering","crispEdges"),null!==b.gridlineShapes&&g(b.gridlineShapes.selectAll(".gridlines line")).style("fill","none").style("stroke","lightgray").style("opacity",.8)),(null===b.measure||void 0===b.measure)&&(b===c?(m=0,b.shapes.selectAll(".axis text").each(function(){var a=this.getComputedTextLength();m=a>m?a:m}),m>this.width/b.shapes.selectAll(".axis text")[0].length&&(l=!0,b.shapes.selectAll(".axis 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)")}))):"x"===b.position&&(m=0,b.shapes.selectAll(".axis text").each(function(){var a=this.getComputedTextLength();m=a>m?a:m}),m>this.width/b.shapes.selectAll(".axis text")[0].length&&(l=!0,b.shapes.selectAll(".axis 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)")})))),null===b.titleShape&&null!==b.shapes&&null!==b.shapes.node().firstChild&&(b.shapes.selectAll(".axis text").each(function(){var a=this.getBBox();(null===n.l||-9-a.widthn.r)&&(n.r=a.x+a.width),l?((null===n.t||a.y+a.height-a.widthn.b)&&(n.b=a.height+a.width)):((null===n.t||a.yn.b)&&(n.b=9+a.height))}),"x"===b.position?(p=b===c?this.y+this.height+n.b+5:this.y+n.t-10,o=this.x+this.width/2):"y"===b.position&&(o=b===d?this.x+n.l-10:this.x+this.width+n.r+20,p=this.y+this.height/2,q="rotate(270, "+o+", "+p+")"),b.titleShape=this._group.append("text").attr("class","axis title"),b.titleShape.attr("x",o).attr("y",p).attr("text-anchor","middle").attr("transform",q).text(null===b.categoryFields||void 0===b.categoryFields||0===b.categoryFields.length?b.measure:b.categoryFields.join("/")).each(function(){r.noFormats||d3.select(this).style("font-family","sans-serif").style("font-size",(r.height/35>10?r.height/35:10)+"px")}),b===c?b.titleShape.each(function(){d3.select(this).attr("y",p+this.getBBox().height/1.65)}):b===d&&b.titleShape.each(function(){d3.select(this).attr("x",o+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=dimple._parsePosition(a,this.svg.node().offsetWidth),this.y=dimple._parsePosition(b,this.svg.node().offsetHeight),this.width=dimple._parsePosition(c,this.svg.node().offsetWidth),this.height=dimple._parsePosition(d,this.svg.node().offsetHeight),this.draw(),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=dimple._parsePosition(b,this.chart.svg.node().offsetWidth),this.y=dimple._parsePosition(c,this.chart.svg.node().offsetHeight),this.width=dimple._parsePosition(d,this.chart.svg.node().offsetWidth),this.height=dimple._parsePosition(e,this.chart.svg.node().offsetHeight),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.height/35>10?j.chart.height/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.width&&(f=0,g+=e),g>j.height?d3.select(this).remove():(d3.select(this).select("text").attr("x","left"===j.horizontalAlign?j.x+h+5+f:j.x+(j.width-f-d)+h+5).attr("y",function(){return j.y+g+this.getBBox().height/1.65}).attr("width",j.width).attr("height",j.height),d3.select(this).select("rect").attr("class","legend legendKey").attr("x","left"===j.horizontalAlign?j.x+f:j.x+(j.width-f-d)).attr("y",j.y+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.x:a._origin:"y"===a.position&&null===d.y&&(d.y=a._hasTimeField()?this.chart.y+this.chart.height: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.x+this.chart.width)),b+=1):(e===this.x&&(0===a?c.y=d.y:1===a&&(c.y=this.chart.y)),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,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.x+.01*this.chart.width).attr("y",this.chart.y+(this.chart.height/35>10?this.chart.height/35:10)*(c>1?1.25:-1)).call(function(){b.noFormats||this.style("font-family","sans-serif").style("font-size",(b.height/35>10?b.height/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.width/i.length/2:0).attr("y1","y"===c.position?c._scale(i[0])-e.height/i.length/2:0).attr("x2","x"===c.position?c._scale(i[i.length-1])+e.width/i.length/2:0).attr("y2","y"===c.position?c._scale(i[i.length-1])-e.height/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.width/c.x._max-2*dimple._helpers.xGap(b,c))*a.width:c.x._scale(a.cx)+b.width/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.height/c.y._max+dimple._helpers.yGap(b,c)+(a.yOffset+.5)*(b.height/c.y._max-2*dimple._helpers.yGap(b,c))*a.height:c.y._scale(a.cy)-b.height/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.height/100)},xGap:function(a,b){var c=0;return(null===b.x.measure||void 0===b.x.measure)&&b.barGap>0&&(c=a.width/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.width/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.height/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.height/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.height/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.width/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.height/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._parsePosition=function(a,b){var c=a;return isNaN(a)?"%"===a.slice(-1)?c=b*(parseFloat(a.slice(0,a.length-1))/100):"px"===a.slice(-2)&&(c=parseFloat(a.slice(0,a.length-2))):c=a,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/dist/dimple.v1.1.0.js b/dist/dimple.v1.1.0.js new file mode 100644 index 0000000..ee5b152 --- /dev/null +++ b/dist/dimple.v1.1.0.js @@ -0,0 +1,4149 @@ +// 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.0.0", + 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) { + // 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 + 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/_xPixels.js + // Access the pixel value of the height of the plot area + this._heightPixels = function () { + return dimple._parsePosition(this.height, this.svg.node().offsetHeight); + }; + + // 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/_xPixels.js + // Access the pixel value of the width of the plot area + this._widthPixels = function () { + return dimple._parsePosition(this.width, this.svg.node().offsetWidth); + }; + // 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._parsePosition(this.x, this.svg.node().offsetWidth); + }; + // 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._parsePosition(this.y, this.svg.node().offsetHeight); + }; + // 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"), + 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") + ]; + // 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; + + // 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) : this._yPixels() + this._heightPixels()) + ")"; + gridTransform = "translate(0, " + (axis === firstX ? this._yPixels() + this._heightPixels() : this._yPixels()) + ")"; + gridSize = -this._heightPixels(); + } else if (axis === firstY && firstX !== null) { + transform = "translate(" + (firstX.categoryFields === null || firstX.categoryFields.length === 0 ? firstX._scale(0) : this._xPixels()) + ", 0)"; + gridTransform = "translate(" + (axis === firstY ? this._xPixels() : this._xPixels() + this._widthPixels()) + ", 0)"; + gridSize = -this._widthPixels(); + } else if (axis.position === "x") { + gridTransform = transform = "translate(0, " + (axis === firstX ? this._yPixels() + this._heightPixels() : this._yPixels()) + ")"; + gridSize = -this._heightPixels(); + } else if (axis.position === "y") { + gridTransform = transform = "translate(" + (axis === firstY ? this._xPixels() : this._xPixels() + this._widthPixels()) + ", 0)"; + gridSize = -this._widthPixels(); + } + // 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(".axis text")).attr("x", (this._widthPixels() / axis._max) / 2); + } else if (axis.position === "y") { + handleTrans(axis.shapes.selectAll(".axis text")).attr("y", -1 * (this._heightPixels() / 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(".axis text")).attr("y", this._yPixels() + this._heightPixels() - firstY._scale(0) + 9); + } + if (axis === firstY && (firstX.categoryFields === null || firstX.categoryFields.length === 0)) { + handleTrans(axis.shapes.selectAll(".axis text")).attr("x", -1 * (firstX._scale(0) - this._xPixels()) - 9); + } + } + } + // Set some initial css values + if (!this.noFormats) { + handleTrans(axis.shapes.selectAll(".axis text")) + .style("font-family", "sans-serif") + .style("font-size", (this._heightPixels() / 35 > 10 ? this._heightPixels() / 35 : 10) + "px"); + handleTrans(axis.shapes.selectAll(".axis path, .axis line")) + .style("fill", "none") + .style("stroke", "black") + .style("shape-rendering", "crispEdges"); + if (axis.gridlineShapes !== null) { + handleTrans(axis.gridlineShapes.selectAll(".gridlines 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(".axis text").each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > this._widthPixels() / axis.shapes.selectAll(".axis text")[0].length) { + rotated = true; + axis.shapes.selectAll(".axis 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 if (axis.position === "x") { + // If the gaps are narrower than the widest label display all labels horizontally + widest = 0; + axis.shapes.selectAll(".axis text") + .each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > this._widthPixels() / axis.shapes.selectAll(".axis text")[0].length) { + rotated = true; + axis.shapes.selectAll(".axis 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)"); + }); + } + } + } + if (axis.titleShape === null && axis.shapes !== null && axis.shapes.node().firstChild !== null) { + // Get the bounds of the axis objects + axis.shapes.selectAll(".axis 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 = this._yPixels() + this._heightPixels() + box.b + 5; + } else { + titleY = this._yPixels() + box.t - 10; + } + titleX = this._xPixels() + (this._widthPixels() / 2); + } else if (axis.position === "y") { + if (axis === firstY) { + titleX = this._xPixels() + box.l - 10; + } else { + titleX = this._xPixels() + this._widthPixels() + box.r + 20; + } + titleY = this._yPixels() + (this._heightPixels() / 2); + rotate = "rotate(270, " + titleX + ", " + titleY + ")"; + } + + // Add a title for the axis + 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", (chart._heightPixels() / 35 > 10 ? chart._heightPixels() / 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._parsePosition(this.x, this.svg.node().offsetWidth); + }; + // Access the pixel value of the y coordinate + this._yPixels = function () { + return dimple._parsePosition(this.y, this.svg.node().offsetHeight); + }; + // Access the pixel value of the width coordinate + this._widthPixels = function () { + return dimple._parsePosition(this.width, this.svg.node().offsetWidth); + }; + // Access the pixel value of the width coordinate + this._heightPixels = function () { + return dimple._parsePosition(this.height, this.svg.node().offsetHeight); + }; + // 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._parsePosition(this.x, this.svg.node().offsetWidth); + }; + // Access the pixel value of the y coordinate + this._yPixels = function () { + return dimple._parsePosition(this.y, this.svg.node().offsetHeight); + }; + // Access the pixel value of the width coordinate + this._widthPixels = function () { + return this.svg.node().offsetWidth - this._xPixels() - dimple._parsePosition(right, this.svg.node().offsetWidth); + }; + // Access the pixel value of the width coordinate + this._heightPixels = function () { + return this.svg.node().offsetHeight - this._yPixels() - dimple._parsePosition(bottom, this.svg.node().offsetHeight); + }; + // 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/chart/methods/_xPixels.js + // Access the pixel value of the height of the plot area + this._heightPixels = function () { + return dimple._parsePosition(this.height, this.chart.svg.node().offsetHeight); + }; + + // 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 value of the width of the plot area + this._widthPixels = function () { + return dimple._parsePosition(this.width, this.chart.svg.node().offsetWidth); + }; + // 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._parsePosition(this.x, this.chart.svg.node().offsetWidth); + }; + // 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._parsePosition(this.y, this.chart.svg.node().offsetHeight); + }; + }; + // 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/_parsePosition.js + dimple._parsePosition = function (value, svgScaleValue) { + var returnValue = value; + if (value === undefined || value === null) { + returnValue = 0; + } else if (!isNaN(value)) { + returnValue = value; + } else if (value.slice(-1) === "%") { + returnValue = svgScaleValue * (parseFloat(value.slice(0, value.length - 1)) / 100); + } else if (value.slice(-2) === "px") { + returnValue = parseFloat(value.slice(0, value.length - 2)); + } + 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.0.min.js b/dist/dimple.v1.1.0.min.js new file mode 100644 index 0000000..0308b59 --- /dev/null +++ b/dist/dimple.v1.1.0.min.js @@ -0,0 +1,2 @@ +var dimple={version:"1.0.0",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?Date.parse(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=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._parsePosition(this.height,this.svg.node().offsetHeight)},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._parsePosition(this.width,this.svg.node().offsetWidth)},this._xPixels=function(){return dimple._parsePosition(this.x,this.svg.node().offsetWidth)},this._yPixels=function(){return dimple._parsePosition(this.y,this.svg.node().offsetHeight)},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;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,h=!1,i=null,j=0,k=null,l=!1,m=0,n={l:null,t:null,r:null,b:null},o=0,p=0,q="",r=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"),h=!0),b===d&&null!==e?(i="translate(0, "+(null===e.categoryFields||0===e.categoryFields.length?e._scale(0):this._yPixels()+this._heightPixels())+")",k="translate(0, "+(b===d?this._yPixels()+this._heightPixels():this._yPixels())+")",j=-this._heightPixels()):b===e&&null!==d?(i="translate("+(null===d.categoryFields||0===d.categoryFields.length?d._scale(0):this._xPixels())+", 0)",k="translate("+(b===e?this._xPixels():this._xPixels()+this._widthPixels())+", 0)",j=-this._widthPixels()):"x"===b.position?(k=i="translate(0, "+(b===d?this._yPixels()+this._heightPixels():this._yPixels())+")",j=-this._heightPixels()):"y"===b.position&&(k=i="translate("+(b===e?this._xPixels():this._xPixels()+this._widthPixels())+", 0)",j=-this._widthPixels()),c=function(b){var c;return c=null===i||0===a||h?b:b.transition().duration(a)},null!==i&&null!==b._draw&&(b._hasTimeField()?c(b.shapes).call(b._draw.ticks(b._getTimePeriod(),b.timeInterval).tickFormat(b._getFormat())).attr("transform",i):b.useLog?c(b.shapes).call(b._draw.ticks(4,b._getFormat())).attr("transform",i):c(b.shapes).call(b._draw.tickFormat(b._getFormat())).attr("transform",i),null!==b.gridlineShapes&&c(b.gridlineShapes).call(b._draw.tickSize(j,0,0).tickFormat("")).attr("transform",k),(null===b.measure||void 0===b.measure)&&("x"===b.position?c(b.shapes.selectAll(".axis text")).attr("x",this._widthPixels()/b._max/2):"y"===b.position&&c(b.shapes.selectAll(".axis text")).attr("y",-1*(this._heightPixels()/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(".axis text")).attr("y",this._yPixels()+this._heightPixels()-e._scale(0)+9),b!==e||null!==d.categoryFields&&0!==d.categoryFields.length||c(b.shapes.selectAll(".axis text")).attr("x",-1*(d._scale(0)-this._xPixels())-9))),this.noFormats||(c(b.shapes.selectAll(".axis text")).style("font-family","sans-serif").style("font-size",(this._heightPixels()/35>10?this._heightPixels()/35:10)+"px"),c(b.shapes.selectAll(".axis path, .axis line")).style("fill","none").style("stroke","black").style("shape-rendering","crispEdges"),null!==b.gridlineShapes&&c(b.gridlineShapes.selectAll(".gridlines line")).style("fill","none").style("stroke","lightgray").style("opacity",.8)),(null===b.measure||void 0===b.measure)&&(b===d?(m=0,b.shapes.selectAll(".axis text").each(function(){var a=this.getComputedTextLength();m=a>m?a:m}),m>this._widthPixels()/b.shapes.selectAll(".axis text")[0].length&&(l=!0,b.shapes.selectAll(".axis 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)")}))):"x"===b.position&&(m=0,b.shapes.selectAll(".axis text").each(function(){var a=this.getComputedTextLength();m=a>m?a:m}),m>this._widthPixels()/b.shapes.selectAll(".axis text")[0].length&&(l=!0,b.shapes.selectAll(".axis 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)")})))),null===b.titleShape&&null!==b.shapes&&null!==b.shapes.node().firstChild&&(b.shapes.selectAll(".axis text").each(function(){var a=this.getBBox();(null===n.l||-9-a.widthn.r)&&(n.r=a.x+a.width),l?((null===n.t||a.y+a.height-a.widthn.b)&&(n.b=a.height+a.width)):((null===n.t||a.yn.b)&&(n.b=9+a.height))}),"x"===b.position?(p=b===d?this._yPixels()+this._heightPixels()+n.b+5:this._yPixels()+n.t-10,o=this._xPixels()+this._widthPixels()/2):"y"===b.position&&(o=b===e?this._xPixels()+n.l-10:this._xPixels()+this._widthPixels()+n.r+20,p=this._yPixels()+this._heightPixels()/2,q="rotate(270, "+o+", "+p+")"),b.titleShape=this._group.append("text").attr("class","axis title"),b.titleShape.attr("x",o).attr("y",p).attr("text-anchor","middle").attr("transform",q).text(null===b.categoryFields||void 0===b.categoryFields||0===b.categoryFields.length?b.measure:b.categoryFields.join("/")).each(function(){r.noFormats||d3.select(this).style("font-family","sans-serif").style("font-size",(r._heightPixels()/35>10?r._heightPixels()/35:10)+"px")}),b===d?b.titleShape.each(function(){d3.select(this).attr("y",p+this.getBBox().height/1.65)}):b===e&&b.titleShape.each(function(){d3.select(this).attr("x",o+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._parsePosition(this.x,this.svg.node().offsetWidth)},this._yPixels=function(){return dimple._parsePosition(this.y,this.svg.node().offsetHeight)},this._widthPixels=function(){return dimple._parsePosition(this.width,this.svg.node().offsetWidth)},this._heightPixels=function(){return dimple._parsePosition(this.height,this.svg.node().offsetHeight)},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._parsePosition(this.x,this.svg.node().offsetWidth)},this._yPixels=function(){return dimple._parsePosition(this.y,this.svg.node().offsetHeight)},this._widthPixels=function(){return this.svg.node().offsetWidth-this._xPixels()-dimple._parsePosition(c,this.svg.node().offsetWidth)},this._heightPixels=function(){return this.svg.node().offsetHeight-this._yPixels()-dimple._parsePosition(d,this.svg.node().offsetHeight)},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._parsePosition=function(a,b){var c=a;return void 0===a||null===a?c=0:isNaN(a)?"%"===a.slice(-1)?c=b*(parseFloat(a.slice(0,a.length-1))/100):"px"===a.slice(-2)&&(c=parseFloat(a.slice(0,a.length-2))):c=a,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/dist/dimple.v1.js b/dist/dimple.v1.js new file mode 100644 index 0000000..fda42b4 --- /dev/null +++ b/dist/dimple.v1.js @@ -0,0 +1,4100 @@ +// 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.0.0", + 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) { + // 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 + 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; + // Access the pixel value of the x coordinate + this._xPixels = function () { + return dimple._parsePosition(this.x, this.svg.node().offsetWidth); + }; + // Access the pixel value of the y coordinate + this._yPixels = function () { + return dimple._parsePosition(this.y, this.svg.node().offsetHeight); + }; + // Access the pixel value of the width coordinate + this._widthPixels = function () { + return dimple._parsePosition(this.width, this.svg.node().offsetWidth); + }; + // Access the pixel value of the height coordinate + this._heightPixels = function () { + return dimple._parsePosition(this.height, this.svg.node().offsetHeight); + }; + // 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/_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/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"), + 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") + ]; + // 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; + + // 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) : this._yPixels() + this._heightPixels()) + ")"; + gridTransform = "translate(0, " + (axis === firstX ? this._yPixels() + this._heightPixels() : this._yPixels()) + ")"; + gridSize = -this._heightPixels(); + } else if (axis === firstY && firstX !== null) { + transform = "translate(" + (firstX.categoryFields === null || firstX.categoryFields.length === 0 ? firstX._scale(0) : this._xPixels()) + ", 0)"; + gridTransform = "translate(" + (axis === firstY ? this._xPixels() : this._xPixels() + this._widthPixels()) + ", 0)"; + gridSize = -this._widthPixels(); + } else if (axis.position === "x") { + gridTransform = transform = "translate(0, " + (axis === firstX ? this._yPixels() + this._heightPixels() : this._yPixels()) + ")"; + gridSize = -this._heightPixels(); + } else if (axis.position === "y") { + gridTransform = transform = "translate(" + (axis === firstY ? this._xPixels() : this._xPixels() + this._widthPixels()) + ", 0)"; + gridSize = -this._widthPixels(); + } + // 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(".axis text")).attr("x", (this._widthPixels() / axis._max) / 2); + } else if (axis.position === "y") { + handleTrans(axis.shapes.selectAll(".axis text")).attr("y", -1 * (this._heightPixels() / 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(".axis text")).attr("y", this._yPixels() + this._heightPixels() - firstY._scale(0) + 9); + } + if (axis === firstY && (firstX.categoryFields === null || firstX.categoryFields.length === 0)) { + handleTrans(axis.shapes.selectAll(".axis text")).attr("x", -1 * (firstX._scale(0) - this._xPixels()) - 9); + } + } + } + // Set some initial css values + if (!this.noFormats) { + handleTrans(axis.shapes.selectAll(".axis text")) + .style("font-family", "sans-serif") + .style("font-size", (this._heightPixels() / 35 > 10 ? this._heightPixels() / 35 : 10) + "px"); + handleTrans(axis.shapes.selectAll(".axis path, .axis line")) + .style("fill", "none") + .style("stroke", "black") + .style("shape-rendering", "crispEdges"); + if (axis.gridlineShapes !== null) { + handleTrans(axis.gridlineShapes.selectAll(".gridlines 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(".axis text").each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > this._widthPixels() / axis.shapes.selectAll(".axis text")[0].length) { + rotated = true; + axis.shapes.selectAll(".axis 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 if (axis.position === "x") { + // If the gaps are narrower than the widest label display all labels horizontally + widest = 0; + axis.shapes.selectAll(".axis text") + .each(function () { + var w = this.getComputedTextLength(); + widest = (w > widest ? w : widest); + }); + if (widest > this._widthPixels() / axis.shapes.selectAll(".axis text")[0].length) { + rotated = true; + axis.shapes.selectAll(".axis 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)"); + }); + } + } + } + if (axis.titleShape === null && axis.shapes !== null && axis.shapes.node().firstChild !== null) { + // Get the bounds of the axis objects + axis.shapes.selectAll(".axis 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 = this._yPixels() + this._heightPixels() + box.b + 5; + } else { + titleY = this._yPixels() + box.t - 10; + } + titleX = this._xPixels() + (this._widthPixels() / 2); + } else if (axis.position === "y") { + if (axis === firstY) { + titleX = this._xPixels() + box.l - 10; + } else { + titleX = this._xPixels() + this._widthPixels() + box.r + 20; + } + titleY = this._yPixels() + (this._heightPixels() / 2); + rotate = "rotate(270, " + titleX + ", " + titleY + ")"; + } + + // Add a title for the axis + 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", (chart._heightPixels() / 35 > 10 ? chart._heightPixels() / 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._parsePosition(this.x, this.svg.node().offsetWidth); + }; + // Access the pixel value of the y coordinate + this._yPixels = function () { + return dimple._parsePosition(this.y, this.svg.node().offsetHeight); + }; + // Access the pixel value of the width coordinate + this._widthPixels = function () { + return dimple._parsePosition(this.width, this.svg.node().offsetWidth); + }; + // Access the pixel value of the width coordinate + this._heightPixels = function () { + return dimple._parsePosition(this.height, this.svg.node().offsetHeight); + }; + // 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._parsePosition(this.x, this.svg.node().offsetWidth); + }; + // Access the pixel value of the y coordinate + this._yPixels = function () { + return dimple._parsePosition(this.y, this.svg.node().offsetHeight); + }; + // Access the pixel value of the width coordinate + this._widthPixels = function () { + return this.svg.node().offsetWidth - this._xPixels() - dimple._parsePosition(right, this.svg.node().offsetWidth); + }; + // Access the pixel value of the width coordinate + this._heightPixels = function () { + return this.svg.node().offsetHeight - this._yPixels() - dimple._parsePosition(bottom, this.svg.node().offsetHeight); + }; + // 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 = dimple._parsePosition(x, this.chart.svg.node().offsetWidth); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-y + this.y = dimple._parsePosition(y, this.chart.svg.node().offsetHeight); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-width + this.width = dimple._parsePosition(width, this.chart.svg.node().offsetWidth); + // Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.legend#wiki-height + this.height = dimple._parsePosition(height, this.chart.svg.node().offsetHeight); + // 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; + // Source: /src/objects/legend/methods/_draw.js + 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.width) { + runningX = 0; + runningY += maxHeight; + } + if (runningY > self.height) { + d3.select(this).remove(); + } else { + d3.select(this).select("text") + .attr("x", (self.horizontalAlign === "left" ? self.x + keyWidth + 5 + runningX : self.x + (self.width - 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.y + runningY + this.getBBox().height / 1.65; + }) + .attr("width", self.width) + .attr("height", self.height); + d3.select(this).select("rect") + .attr("class", "legend legendKey") + .attr("x", (self.horizontalAlign === "left" ? self.x + runningX : self.x + (self.width - runningX - maxWidth))) + .attr("y", self.y + 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; + }; + + // Source: /src/objects/legend/methods/_getEntries.js + 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; + }; + + }; + // 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/_parsePosition.js + dimple._parsePosition = function (value, svgScaleValue) { + var returnValue = value; + if (value === undefined || value === null) { + returnValue = 0; + } else if (!isNaN(value)) { + returnValue = value; + } else if (value.slice(-1) === "%") { + returnValue = svgScaleValue * (parseFloat(value.slice(0, value.length - 1)) / 100); + } else if (value.slice(-2) === "px") { + returnValue = parseFloat(value.slice(0, value.length - 2)); + } + 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.min.js b/dist/dimple.v1.min.js new file mode 100644 index 0000000..a2e9186 --- /dev/null +++ b/dist/dimple.v1.min.js @@ -0,0 +1,2 @@ +var dimple={version:"1.0.0",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?Date.parse(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=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._xPixels=function(){return dimple._parsePosition(this.x,this.svg.node().offsetWidth)},this._yPixels=function(){return dimple._parsePosition(this.y,this.svg.node().offsetHeight)},this._widthPixels=function(){return dimple._parsePosition(this.width,this.svg.node().offsetWidth)},this._heightPixels=function(){return dimple._parsePosition(this.height,this.svg.node().offsetHeight)},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._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.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;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,h=!1,i=null,j=0,k=null,l=!1,m=0,n={l:null,t:null,r:null,b:null},o=0,p=0,q="",r=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"),h=!0),b===d&&null!==e?(i="translate(0, "+(null===e.categoryFields||0===e.categoryFields.length?e._scale(0):this._yPixels()+this._heightPixels())+")",k="translate(0, "+(b===d?this._yPixels()+this._heightPixels():this._yPixels())+")",j=-this._heightPixels()):b===e&&null!==d?(i="translate("+(null===d.categoryFields||0===d.categoryFields.length?d._scale(0):this._xPixels())+", 0)",k="translate("+(b===e?this._xPixels():this._xPixels()+this._widthPixels())+", 0)",j=-this._widthPixels()):"x"===b.position?(k=i="translate(0, "+(b===d?this._yPixels()+this._heightPixels():this._yPixels())+")",j=-this._heightPixels()):"y"===b.position&&(k=i="translate("+(b===e?this._xPixels():this._xPixels()+this._widthPixels())+", 0)",j=-this._widthPixels()),c=function(b){var c;return c=null===i||0===a||h?b:b.transition().duration(a)},null!==i&&null!==b._draw&&(b._hasTimeField()?c(b.shapes).call(b._draw.ticks(b._getTimePeriod(),b.timeInterval).tickFormat(b._getFormat())).attr("transform",i):b.useLog?c(b.shapes).call(b._draw.ticks(4,b._getFormat())).attr("transform",i):c(b.shapes).call(b._draw.tickFormat(b._getFormat())).attr("transform",i),null!==b.gridlineShapes&&c(b.gridlineShapes).call(b._draw.tickSize(j,0,0).tickFormat("")).attr("transform",k),(null===b.measure||void 0===b.measure)&&("x"===b.position?c(b.shapes.selectAll(".axis text")).attr("x",this._widthPixels()/b._max/2):"y"===b.position&&c(b.shapes.selectAll(".axis text")).attr("y",-1*(this._heightPixels()/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(".axis text")).attr("y",this._yPixels()+this._heightPixels()-e._scale(0)+9),b!==e||null!==d.categoryFields&&0!==d.categoryFields.length||c(b.shapes.selectAll(".axis text")).attr("x",-1*(d._scale(0)-this._xPixels())-9))),this.noFormats||(c(b.shapes.selectAll(".axis text")).style("font-family","sans-serif").style("font-size",(this._heightPixels()/35>10?this._heightPixels()/35:10)+"px"),c(b.shapes.selectAll(".axis path, .axis line")).style("fill","none").style("stroke","black").style("shape-rendering","crispEdges"),null!==b.gridlineShapes&&c(b.gridlineShapes.selectAll(".gridlines line")).style("fill","none").style("stroke","lightgray").style("opacity",.8)),(null===b.measure||void 0===b.measure)&&(b===d?(m=0,b.shapes.selectAll(".axis text").each(function(){var a=this.getComputedTextLength();m=a>m?a:m}),m>this._widthPixels()/b.shapes.selectAll(".axis text")[0].length&&(l=!0,b.shapes.selectAll(".axis 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)")}))):"x"===b.position&&(m=0,b.shapes.selectAll(".axis text").each(function(){var a=this.getComputedTextLength();m=a>m?a:m}),m>this._widthPixels()/b.shapes.selectAll(".axis text")[0].length&&(l=!0,b.shapes.selectAll(".axis 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)")})))),null===b.titleShape&&null!==b.shapes&&null!==b.shapes.node().firstChild&&(b.shapes.selectAll(".axis text").each(function(){var a=this.getBBox();(null===n.l||-9-a.widthn.r)&&(n.r=a.x+a.width),l?((null===n.t||a.y+a.height-a.widthn.b)&&(n.b=a.height+a.width)):((null===n.t||a.yn.b)&&(n.b=9+a.height))}),"x"===b.position?(p=b===d?this._yPixels()+this._heightPixels()+n.b+5:this._yPixels()+n.t-10,o=this._xPixels()+this._widthPixels()/2):"y"===b.position&&(o=b===e?this._xPixels()+n.l-10:this._xPixels()+this._widthPixels()+n.r+20,p=this._yPixels()+this._heightPixels()/2,q="rotate(270, "+o+", "+p+")"),b.titleShape=this._group.append("text").attr("class","axis title"),b.titleShape.attr("x",o).attr("y",p).attr("text-anchor","middle").attr("transform",q).text(null===b.categoryFields||void 0===b.categoryFields||0===b.categoryFields.length?b.measure:b.categoryFields.join("/")).each(function(){r.noFormats||d3.select(this).style("font-family","sans-serif").style("font-size",(r._heightPixels()/35>10?r._heightPixels()/35:10)+"px")}),b===d?b.titleShape.each(function(){d3.select(this).attr("y",p+this.getBBox().height/1.65)}):b===e&&b.titleShape.each(function(){d3.select(this).attr("x",o+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._parsePosition(this.x,this.svg.node().offsetWidth)},this._yPixels=function(){return dimple._parsePosition(this.y,this.svg.node().offsetHeight)},this._widthPixels=function(){return dimple._parsePosition(this.width,this.svg.node().offsetWidth)},this._heightPixels=function(){return dimple._parsePosition(this.height,this.svg.node().offsetHeight)},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._parsePosition(this.x,this.svg.node().offsetWidth)},this._yPixels=function(){return dimple._parsePosition(this.y,this.svg.node().offsetHeight)},this._widthPixels=function(){return this.svg.node().offsetWidth-this._xPixels()-dimple._parsePosition(c,this.svg.node().offsetWidth)},this._heightPixels=function(){return this.svg.node().offsetHeight-this._yPixels()-dimple._parsePosition(d,this.svg.node().offsetHeight)},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=dimple._parsePosition(b,this.chart.svg.node().offsetWidth),this.y=dimple._parsePosition(c,this.chart.svg.node().offsetHeight),this.width=dimple._parsePosition(d,this.chart.svg.node().offsetWidth),this.height=dimple._parsePosition(e,this.chart.svg.node().offsetHeight),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.width&&(f=0,g+=e),g>j.height?d3.select(this).remove():(d3.select(this).select("text").attr("x","left"===j.horizontalAlign?j.x+h+5+f:j.x+(j.width-f-d)+h+5).attr("y",function(){return j.y+g+this.getBBox().height/1.65}).attr("width",j.width).attr("height",j.height),d3.select(this).select("rect").attr("class","legend legendKey").attr("x","left"===j.horizontalAlign?j.x+f:j.x+(j.width-f-d)).attr("y",j.y+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._parsePosition=function(a,b){var c=a;return void 0===a||null===a?c=0:isNaN(a)?"%"===a.slice(-1)?c=b*(parseFloat(a.slice(0,a.length-1))/100):"px"===a.slice(-2)&&(c=parseFloat(a.slice(0,a.length-2))):c=a,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 3a72267..6d223e4 100644 --- a/examples/advanced_bar_labels.html +++ b/examples/advanced_bar_labels.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_bullet.html b/examples/advanced_bullet.html index 698302f..354031f 100644 --- a/examples/advanced_bullet.html +++ b/examples/advanced_bullet.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_dynamic_line_color.html b/examples/advanced_dynamic_line_color.html index 6a7b09c..6478096 100644 --- a/examples/advanced_dynamic_line_color.html +++ b/examples/advanced_dynamic_line_color.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_grouped_mekko.html b/examples/advanced_grouped_mekko.html index bbc21b7..2c701a4 100644 --- a/examples/advanced_grouped_mekko.html +++ b/examples/advanced_grouped_mekko.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_interactive_legends.html b/examples/advanced_interactive_legends.html index 17e6baa..a633e4e 100644 --- a/examples/advanced_interactive_legends.html +++ b/examples/advanced_interactive_legends.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_lollipop_with_hover.html b/examples/advanced_lollipop_with_hover.html index d5a9b9f..76bae8d 100644 --- a/examples/advanced_lollipop_with_hover.html +++ b/examples/advanced_lollipop_with_hover.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_matrix.html b/examples/advanced_matrix.html index 118ce1b..64a3d23 100644 --- a/examples/advanced_matrix.html +++ b/examples/advanced_matrix.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_pong.html b/examples/advanced_pong.html index 5d4ef67..47e32e5 100644 --- a/examples/advanced_pong.html +++ b/examples/advanced_pong.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_price_range_lollipop.html b/examples/advanced_price_range_lollipop.html index dd12a2c..aae81e9 100644 --- a/examples/advanced_price_range_lollipop.html +++ b/examples/advanced_price_range_lollipop.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_storyboard_control.html b/examples/advanced_storyboard_control.html index d8aeca3..1683d73 100644 --- a/examples/advanced_storyboard_control.html +++ b/examples/advanced_storyboard_control.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_time_axis.html b/examples/advanced_time_axis.html index 585b6ee..da95f59 100644 --- a/examples/advanced_time_axis.html +++ b/examples/advanced_time_axis.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_trellis_bar.html b/examples/advanced_trellis_bar.html index 04c91b9..79420a0 100644 --- a/examples/advanced_trellis_bar.html +++ b/examples/advanced_trellis_bar.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/advanced_waterfall.html b/examples/advanced_waterfall.html index 788234c..2b60213 100644 --- a/examples/advanced_waterfall.html +++ b/examples/advanced_waterfall.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_horizontal.html b/examples/areas_horizontal.html index f350456..f15af51 100644 --- a/examples/areas_horizontal.html +++ b/examples/areas_horizontal.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_horizontal_grouped.html b/examples/areas_horizontal_grouped.html index 69e9d38..f353fba 100644 --- a/examples/areas_horizontal_grouped.html +++ b/examples/areas_horizontal_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_horizontal_grouped_100pct.html b/examples/areas_horizontal_grouped_100pct.html index 3fc7ac4..51682c6 100644 --- a/examples/areas_horizontal_grouped_100pct.html +++ b/examples/areas_horizontal_grouped_100pct.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_horizontal_grouped_stacked.html b/examples/areas_horizontal_grouped_stacked.html index 530929f..dc10726 100644 --- a/examples/areas_horizontal_grouped_stacked.html +++ b/examples/areas_horizontal_grouped_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_horizontal_stacked.html b/examples/areas_horizontal_stacked.html index 044f987..b247305 100644 --- a/examples/areas_horizontal_stacked.html +++ b/examples/areas_horizontal_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_horizontal_stacked_100pct.html b/examples/areas_horizontal_stacked_100pct.html index 52c778e..385ad7e 100644 --- a/examples/areas_horizontal_stacked_100pct.html +++ b/examples/areas_horizontal_stacked_100pct.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_vertical.html b/examples/areas_vertical.html index f356201..074202d 100644 --- a/examples/areas_vertical.html +++ b/examples/areas_vertical.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_vertical_grouped.html b/examples/areas_vertical_grouped.html index fff1f18..1408cb1 100644 --- a/examples/areas_vertical_grouped.html +++ b/examples/areas_vertical_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_vertical_grouped_100pct.html b/examples/areas_vertical_grouped_100pct.html index e841e21..ce19759 100644 --- a/examples/areas_vertical_grouped_100pct.html +++ b/examples/areas_vertical_grouped_100pct.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_vertical_grouped_stacked.html b/examples/areas_vertical_grouped_stacked.html index e814084..f96d43d 100644 --- a/examples/areas_vertical_grouped_stacked.html +++ b/examples/areas_vertical_grouped_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_vertical_stacked.html b/examples/areas_vertical_stacked.html index 2e897e8..a7ac247 100644 --- a/examples/areas_vertical_stacked.html +++ b/examples/areas_vertical_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/areas_vertical_stacked_100pct.html b/examples/areas_vertical_stacked_100pct.html index 2380dc3..dd0e7df 100644 --- a/examples/areas_vertical_stacked_100pct.html +++ b/examples/areas_vertical_stacked_100pct.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_horizontal.html b/examples/bars_horizontal.html index 5ff0edb..9e6da26 100644 --- a/examples/bars_horizontal.html +++ b/examples/bars_horizontal.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_horizontal_grouped.html b/examples/bars_horizontal_grouped.html index 449fa56..176a4fc 100644 --- a/examples/bars_horizontal_grouped.html +++ b/examples/bars_horizontal_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_horizontal_grouped_stacked.html b/examples/bars_horizontal_grouped_stacked.html index 211257e..680d229 100644 --- a/examples/bars_horizontal_grouped_stacked.html +++ b/examples/bars_horizontal_grouped_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_horizontal_grouped_stacked_100pct.html b/examples/bars_horizontal_grouped_stacked_100pct.html index 3fbbdc9..6869ff6 100644 --- a/examples/bars_horizontal_grouped_stacked_100pct.html +++ b/examples/bars_horizontal_grouped_stacked_100pct.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_horizontal_mekko.html b/examples/bars_horizontal_mekko.html index cfad2f4..fde4f66 100644 --- a/examples/bars_horizontal_mekko.html +++ b/examples/bars_horizontal_mekko.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_horizontal_stacked.html b/examples/bars_horizontal_stacked.html index b972257..524e5df 100644 --- a/examples/bars_horizontal_stacked.html +++ b/examples/bars_horizontal_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_horizontal_stacked_100pct.html b/examples/bars_horizontal_stacked_100pct.html index 0ad8627..631cc5f 100644 --- a/examples/bars_horizontal_stacked_100pct.html +++ b/examples/bars_horizontal_stacked_100pct.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_matrix.html b/examples/bars_matrix.html index 8baa0e6..0dacb0a 100644 --- a/examples/bars_matrix.html +++ b/examples/bars_matrix.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_vertical.html b/examples/bars_vertical.html index 0cf4427..dc5ec87 100644 --- a/examples/bars_vertical.html +++ b/examples/bars_vertical.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_vertical_grouped.html b/examples/bars_vertical_grouped.html index 7dfc0d2..4af15e9 100644 --- a/examples/bars_vertical_grouped.html +++ b/examples/bars_vertical_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_vertical_grouped_stacked.html b/examples/bars_vertical_grouped_stacked.html index 19236b9..99d90f9 100644 --- a/examples/bars_vertical_grouped_stacked.html +++ b/examples/bars_vertical_grouped_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_vertical_grouped_stacked_100pct.html b/examples/bars_vertical_grouped_stacked_100pct.html index c139bb9..c91818e 100644 --- a/examples/bars_vertical_grouped_stacked_100pct.html +++ b/examples/bars_vertical_grouped_stacked_100pct.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_vertical_mekko.html b/examples/bars_vertical_mekko.html index f4aa0da..5daefac 100644 --- a/examples/bars_vertical_mekko.html +++ b/examples/bars_vertical_mekko.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_vertical_stacked.html b/examples/bars_vertical_stacked.html index 3a592b4..01a321e 100644 --- a/examples/bars_vertical_stacked.html +++ b/examples/bars_vertical_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bars_vertical_stacked_100pct.html b/examples/bars_vertical_stacked_100pct.html index 5d300b4..c87552d 100644 --- a/examples/bars_vertical_stacked_100pct.html +++ b/examples/bars_vertical_stacked_100pct.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bubbles_horizontal_grouped.html b/examples/bubbles_horizontal_grouped.html index 492c7e7..b70ac97 100644 --- a/examples/bubbles_horizontal_grouped.html +++ b/examples/bubbles_horizontal_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bubbles_horizontal_lollipop.html b/examples/bubbles_horizontal_lollipop.html index 2697df9..5a17b6d 100644 --- a/examples/bubbles_horizontal_lollipop.html +++ b/examples/bubbles_horizontal_lollipop.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bubbles_matrix.html b/examples/bubbles_matrix.html index 9802492..e8f4494 100644 --- a/examples/bubbles_matrix.html +++ b/examples/bubbles_matrix.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bubbles_standard.html b/examples/bubbles_standard.html index e4f1195..a4c605e 100644 --- a/examples/bubbles_standard.html +++ b/examples/bubbles_standard.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bubbles_vertical_grouped.html b/examples/bubbles_vertical_grouped.html index 9dc38b6..edc7e6a 100644 --- a/examples/bubbles_vertical_grouped.html +++ b/examples/bubbles_vertical_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/bubbles_vertical_lollipop.html b/examples/bubbles_vertical_lollipop.html index 87ff6e6..f2b5a78 100644 --- a/examples/bubbles_vertical_lollipop.html +++ b/examples/bubbles_vertical_lollipop.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/lines_horizontal.html b/examples/lines_horizontal.html index f13e524..bc5c132 100644 --- a/examples/lines_horizontal.html +++ b/examples/lines_horizontal.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/lines_horizontal_grouped.html b/examples/lines_horizontal_grouped.html index 69bcd45..5310b45 100644 --- a/examples/lines_horizontal_grouped.html +++ b/examples/lines_horizontal_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/lines_horizontal_grouped_stacked.html b/examples/lines_horizontal_grouped_stacked.html index b459131..9eb39a4 100644 --- a/examples/lines_horizontal_grouped_stacked.html +++ b/examples/lines_horizontal_grouped_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/lines_horizontal_stacked.html b/examples/lines_horizontal_stacked.html index e6c7bc6..09854dd 100644 --- a/examples/lines_horizontal_stacked.html +++ b/examples/lines_horizontal_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/lines_vertical.html b/examples/lines_vertical.html index 953c5fc..4eed18c 100644 --- a/examples/lines_vertical.html +++ b/examples/lines_vertical.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/lines_vertical_grouped.html b/examples/lines_vertical_grouped.html index 84aef6d..7605562 100644 --- a/examples/lines_vertical_grouped.html +++ b/examples/lines_vertical_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/lines_vertical_grouped_stacked.html b/examples/lines_vertical_grouped_stacked.html index 9bac4e3..98ee2b6 100644 --- a/examples/lines_vertical_grouped_stacked.html +++ b/examples/lines_vertical_grouped_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/lines_vertical_stacked.html b/examples/lines_vertical_stacked.html index 4fbb71f..820c7c4 100644 --- a/examples/lines_vertical_stacked.html +++ b/examples/lines_vertical_stacked.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/scatter_horizontal_grouped.html b/examples/scatter_horizontal_grouped.html index 85aed1a..c3aff5f 100644 --- a/examples/scatter_horizontal_grouped.html +++ b/examples/scatter_horizontal_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/scatter_horizontal_lollipop.html b/examples/scatter_horizontal_lollipop.html index 318709f..5ef7171 100644 --- a/examples/scatter_horizontal_lollipop.html +++ b/examples/scatter_horizontal_lollipop.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/scatter_matrix.html b/examples/scatter_matrix.html index c2dbaec..32ece8f 100644 --- a/examples/scatter_matrix.html +++ b/examples/scatter_matrix.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/scatter_standard.html b/examples/scatter_standard.html index dd94786..77d4ccd 100644 --- a/examples/scatter_standard.html +++ b/examples/scatter_standard.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/scatter_vertical_grouped.html b/examples/scatter_vertical_grouped.html index 01e034b..ca0119f 100644 --- a/examples/scatter_vertical_grouped.html +++ b/examples/scatter_vertical_grouped.html @@ -1,3 +1,6 @@ + + +
diff --git a/examples/scatter_vertical_lollipop.html b/examples/scatter_vertical_lollipop.html index d6e49f9..95540f9 100644 --- a/examples/scatter_vertical_lollipop.html +++ b/examples/scatter_vertical_lollipop.html @@ -1,3 +1,6 @@ + + +
diff --git a/images/advanced_bar_labels.png b/images/advanced_bar_labels.png index 81ba88779452370a9de7cbd3155872e71c4d00fb..b28a9a9120a6082d3e2de1df463f37a25067beb1 100644 GIT binary patch delta 90 zcmZq4Xvx^H$vDW+D8$gf%EZjd$VA(~(8|DIbCmii1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=N0a=a&IBFnGH9xvXKP>f delta 90 zcmZq4Xvx^H$vDWsB*f6t%Gk`x*ht&J(8|CdZRh$$3=9maC9V-ADTyViR=N2pnQ4^_ eMh1q)x(1fI7#hT%h|2*rFnGH9xvXy6v6TTj%`D8$gf%EZjd$U@t|(8|D|y6v6TTj%$B*f6t%Gk`x*hJgF(8|DoHFMUC$rtow6ftB)tr|l!=TYhRoyD#=Hzb;OXk;vd$@? F2>{N?6aN4J diff --git a/images/advanced_grouped_mekko.png b/images/advanced_grouped_mekko.png index d83acd29d7b39c4ca0e5f95cb6a686ffa5733928..6baccbc19001f1d381d26d5779384f6c89027d24 100644 GIT binary patch delta 67 zcmZp(YqQ(1MNZVvD8$gf%EZjd*ht&J(8|C-MnNcV@<}-vMGTqT+EterfWXt$&t;uc GLK6U9$`S1V delta 67 zcmZp(YqQ(1MNZVfB*f6t%Gk`x*h1UD(8|Ez(8&*PCZCj(QN)nhJjEk`0SG)@{an^L HB{Ts5$!HWW diff --git a/images/advanced_interactive_legends.png b/images/advanced_interactive_legends.png index 9355d4312486f1c2adc55d3c996e3365d96b9a87..ae22020fc8b4bd7fd339193849e4855dcfa58420 100644 GIT binary patch delta 90 zcmbOfG%0Aq0gWI-qYy&_D-$y-V-sxyLn{M=`VZp61_lPz64!{5l*E!$tK9sQ%(O}d dBLhQYT?0#93=Q_Kt))N>44$rjF6*2UngBx^7jggq delta 90 zcmbPeJ<)oDy=;(yNr<7Pm9d$XiLthUp_PHbDekrW3=9maC9V-ADTyViR=N2pnQ4^_ eMh1q)x(1fI7#enmiEajJVDNPHb6Mw<&;$ThP#69H diff --git a/images/advanced_matrix.png b/images/advanced_matrix.png index 010c9926d695d0053570293219907750c3cbda50..cba4d066ee271b88523c6da3c091020c6929e634 100644 GIT binary patch delta 90 zcmaE?_gHU(pm>m>QHY^|m5G^^v8A?wp_PGwoVwc-1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=R2J|1^La7(8A5T-G@yGywoua2OB( delta 90 zcmaE?_gHU(pm>mhNr<7Pm9d$XiK(`Mp_PHb%G)zc85kH;OI#yLQW8s2t#b2IGSey< ej0_Bobqy?aF*Mw0Q|JV0VDNPHb6Mw<&;$Ueq!~s4 diff --git a/images/advanced_pong.png b/images/advanced_pong.png index 754df8e498dd26eac6e495a22d2c5b94b4fd8f72..041bc7db8c80d52fc6c13d0f1fb5ad6e4bc44ef4 100644 GIT binary patch delta 66 zcmdnXvX^DUYerE+qYy&_D-$y-6GLqSLn{LVbza6-lbB=_F(kqQc7`zkfv2mV%Q~lo FCICPN5X}Gp delta 66 zcmdnXvX^DUYerE6lMq8oD`PV&6LW0?Ln{LVJC{zGNlY?|7!v-Hh6@;gz|+;wWt~$( F697NA5O)9o diff --git a/images/advanced_price_range_lollipop.png b/images/advanced_price_range_lollipop.png index 14359a16de19a012700e0c644e5de560eca14c1f..7da627004e94cb0266c53fc93c6608e7814ed61d 100644 GIT binary patch delta 67 zcmaDE_A+dPxVEUFQHY^|m5G^^iLthUp_PHbhDid4C+ld-C}PMQJinrz0SG)@{an^L HB{Ts5qqY-q delta 67 zcmaDE_A+dPxVET)Nr<7Pm9d$XiKVuIp_PF_UiBx($vWCHiWoBTYF>XCfWXt$&t;uc GLK6U%oDypQ diff --git a/images/advanced_storyboard_control.png b/images/advanced_storyboard_control.png index 5edd2fa995b69c85d97f9cc76e3d8f02ac67b53d..c34f2af93693f0b5429f8eba814e2a7cbab22be7 100644 GIT binary patch delta 67 zcmbQ8Jv)1Yr>Ur+QHY^|m5G^^iK(`Mp_PHbWhd32ljBTf6ftBL$UrcNr<7Pm9d$XsiC%kp_PHbM7upZCdZk|C}PMwNM>PS00K`}KbLh* G2~7Z#3=&BI diff --git a/images/advanced_time_axis.png b/images/advanced_time_axis.png index 381482302af660544217dc366be19a1189c3a076..462c4d00b5dbe1faa0938da2c62816cc4d8ff73f 100644 GIT binary patch delta 67 zcmcbYaxY~AkCCXMQHY^|m5G^^iMh6cp_PGw%UzQTlNF6*6cI8O5Sha#{Nr<7Pm9d$Xsj;?!p_PFF+rqUICo3AsC}PNz{}I(?00K`}KbLh* G2~7Zy4iZrS diff --git a/images/advanced_trellis_bar.png b/images/advanced_trellis_bar.png index fd2ffb2d1205ebea96d66c3c082086210d8a0292..468e8bf918223286c54accc5b070ef76496500cb 100644 GIT binary patch delta 92 zcmX>wpYgza#tnJ?L54;lh6Yw9W>%&K+6IPJ1_r(txDGHdFsPQeMwFx^mZVzc=BH$) gRWcYE7#iyuSn6VEu<6}Y1JuCa>FVdQ&MBb@0P$iOwpYgza#tnJ?K?WuvhL%>wW>%(V+6IPJ1_mKBo6Hy(7*tDKBT7;dOH!?J^HVa@ gDjAFn42^XSEOjw7s2;KE0%~CJboFyt=akR{0ONTXcK`qY diff --git a/images/advanced_waterfall.png b/images/advanced_waterfall.png index 7681a10a77eef758b06af74526fe5a245a45601e..e2d2b0832f755251babe4e1a1a67fb992c203a4b 100644 GIT binary patch delta 67 zcmexv_}y^BQVCH*qYy&_D-$y-QzLByLn{M=TmI57C-0JwQN)m0Ewnh40SG)@{an^L HB{Ts5ysZ;Y delta 67 zcmexv_}y^BQVCH5lMq8oD`PV&QwwbaLn{LVmSsP)C-0JwQN)lb{q1<20SG)@{an^L HB{Ts5$1fBi diff --git a/images/areas_horizontal.png b/images/areas_horizontal.png index d43e4047da3ba5b4b30d03609437a47e619c95ed..41ec41e9969ab4e16eb05da0273c6fc9305e4dff 100644 GIT binary patch delta 67 zcmX@%cfxPOH#Jd1qYy&_D-$y-Qxk0iLn{M=o%8iBPv%yaQN)lracEjT0}yz+`njxg HN@xNA$3PSG delta 67 zcmX@%cfxPOH#JcMlMq8oD`Rsj0|RXXLm)XJ`%>6sZgm+&44H>!S+5v?z|+;wWt~$( F69Bxj6Py45 diff --git a/images/areas_horizontal_grouped.png b/images/areas_horizontal_grouped.png index 061df4e6c37ebd047abac8832951157a94bbd078..2fa41821ee6b335a07a91acd1fb5f9c442a3c7db 100644 GIT binary patch delta 90 zcmaFp_0VerziN=7QHY^|m5G^^shPHcp_PGw9)tZa1_lPz64!{5l*E!$tK9sQ%(O}d dBLhQYT?0#93=Q$I?QTE~44$rjF6*2UngDlu7!Uve delta 90 zcmaFp_0VerziN%fswX>p_PFF`%&a+6IPJ1_pKZF3Ts^M$0H-$nYoqk!An_Pgg&e IbxsLQ0MRECNdN!< delta 69 zcmdnBopIN8#tj+Kq6Q`*hL%>w=2iwK+6IPJ1_miA-&`lxM$0H-$cR?@{bT?FPgg&e IbxsLQ0NJGzvj6}9 diff --git a/images/areas_horizontal_grouped_stacked.png b/images/areas_horizontal_grouped_stacked.png index 6d6336d4a7ec30cda524e8f874b8a65cd3d965ae..fae5b073392d60b5544d5d4cf0d3c9fe55b89d2a 100644 GIT binary patch delta 69 zcmbQfm~r}I#tp84qJ~Bxh6Yw9=2ixV+6IPJ1_nN@DSDHm0%a62WNdHTnZy7Dp00i_ I>zopr0HcQ!ApigX delta 69 zcmbQfm~r}I#tp84q6Q`*hL%>w=2iyg+6IPJ1_q&L-rSfR6)2;KA)_)eB!vM8JYD@< J);T3K0RYsq6RQ9K diff --git a/images/areas_horizontal_stacked.png b/images/areas_horizontal_stacked.png index 4bf9aa7dadbea0aa67b2f4aa49333cc55d9234fe..c81c7629886c1b2e4d792219e1cb3ce75fdd7f7f 100644 GIT binary patch delta 67 zcmX?=b}DVd4cr9>4i-xG7QHY^|m5I5PfvL8Ep_PHbj#Cb{lV@tkC}PN*oc^Pc0SG)@{an^L HB{Ts5zbX_T delta 67 zcmcZ>cr9>4i-xFyNr<7Pm9e>%p`o^cp_PHbuWdJLCePH6QN)nxew}!L0SG)@{an^L HB{Ts5=57@4 diff --git a/images/areas_vertical.png b/images/areas_vertical.png index 24bbdb3c94d94bf75825411f19a6e1e62536b092..415d7390c00780f2bf0e3350e90d14b56656a9e4 100644 GIT binary patch delta 67 zcmX@@dD?SBrHZJbQHY^|m5I5Pfw{JUp_PGwerTY`%p|Q4sp_PF_uI7o*$$cs^iWo97B?T`TfWXt$&t;uc GLK6U%D-y2& diff --git a/images/areas_vertical_grouped.png b/images/areas_vertical_grouped.png index eb4e176eefe29d3a6891960bb0f3a1a9258707af..e551f7eff166e3c5b38d9e65ba284fd4d5fe777f 100644 GIT binary patch delta 67 zcmaFn`pk916eUqZqYy&_D-&}oLj!FCLn{LVwTOI{$t#s)6ftDNS+DP400K`}KbLh* G2~7Zr;1V|g delta 67 zcmaFn`pk916eUpulMq8oD`RsjLsM-7Ln{LVzMp^dC$ChJQN)m`U*dY10SG)@{an^L HB{Ts5&(0Jj diff --git a/images/areas_vertical_grouped_100pct.png b/images/areas_vertical_grouped_100pct.png index 1493e83ea29e24b60afb5583c0a3f5a57c0cfa75..0115ba4141f57462ceeedff67d8a4a31f7947a77 100644 GIT binary patch delta 69 zcmX@LlJV3^#tjuAqJ~Bxh6Yw9=2nJA+6IPJ1_m3C*Xd5~4UtjAkU22x*hB^(@O1Ta JS?83{1OWL`6!8E6 delta 69 zcmX@LlJV3^#tjuAq6Q`*hL%>w=2nL0+6IPJ1_m?J-ndQf4UtjAka=Mp@{0ioJYD@< J);T3K0Ra0%6x#p* diff --git a/images/areas_vertical_grouped_stacked.png b/images/areas_vertical_grouped_stacked.png index a5e093914289c5fbee4ccd22749901c5038466d8..8b283a257262d02ae120e3a8eb3f5b6a7f540c3a 100644 GIT binary patch delta 92 zcmX>+mGSUY#tnsDL54;lh6Yw9=2nKL+6IPJ1_mVw4vQHW7*tDKBT7;dOH!?J^HVa@ gDjAFn42^XSEOjw7u-g8R2WnvOboFyt=akR{0N2173jhEB delta 92 zcmX>+mGSUY#tnsDK?WuvhL%>w=2k`q+6IPJ1_qb>u4*$dFsPQeMwFx^mZVzc=BH$) gRWcYE7#iyuSn6VESS*}79jJl9)78&qol`;+0Q&M7-v9sr diff --git a/images/areas_vertical_stacked.png b/images/areas_vertical_stacked.png index b38663932cde49b442aa5dced2dd8024305aafbe..65164d6616f7615369813f5f12f8745e5c1cc19d 100644 GIT binary patch delta 67 zcmdm;xI1w}rh%xTQHY^|m5I5Pp}Dq!p_PHb%k&(86p_PGw>%}8Xlj{s*6ftB>dTRDD0D-5gpUXO@ GgeCx>?Go_- diff --git a/images/areas_vertical_stacked_100pct.png b/images/areas_vertical_stacked_100pct.png index 8f5dfc1a85fd5bb3c67918d23212dc58aa565dfb..e65e6b5deff9e08d7718d7cd899f64d1946323a8 100644 GIT binary patch delta 90 zcmZ4Jz0iAuziN=7QHY^|m5I5Pp{2Hgp_PHb<^x*S85kH;OI#yLQW8s2t#b2IGSey< ej0_Bobqy?aF*F>SJ1HBefx*+&&t;ucLK6V7bQ!V$ delta 90 zcmZ4Jz0iAuziN%k%_i}p_PGw8skR`1_lPz64!{5l*E!$tK9sQ%(O}d dBLhQYT?0#93=QEio-IHP44$rjF6*2UngCsQ7rX!f diff --git a/images/bars_horizontal.png b/images/bars_horizontal.png index 2729704a99799c5ac9973c4dad3f3d9b383e4744..dffea495bccddcd68c8f4c2befa2dbbd063ffbd1 100644 GIT binary patch delta 90 zcmZ4JztDe!zj~0NQHY^|m5I5Pk)gJMp_PFF--6^|1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=Q>vZ@&j>VDNPHb6Mw<&;$T_kr{{p delta 90 zcmZ4JztDe!zj}~?Nr<7Pm9e>%k(suEp_PHb#(56}85kH;OI#yLQW8s2t#b2IGSey< ej0_Bobqy?aF*F=F9I+Otfx*+&&t;ucLK6V42N}Wu diff --git a/images/bars_horizontal_grouped.png b/images/bars_horizontal_grouped.png index db811a4990d1fe1e86a02e4fbc820b0937555871..b39908c5cee912d89ac535ce2e8f20f7189cc53a 100644 GIT binary patch delta 90 zcmX@8eo%cwzHpGCQHY^|m5I5Pk+HUcp_PHbO#7lL1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=J=mP8|bkVDNPHb6Mw<&;$UC#Tk46 delta 90 zcmX@8eo%cwzHpF%Nr<7Pm9e>%k%hK_p_PHb!-cbb7#J8-OI#yLQW8s2t#b2IGSey< ej0_Bobqy?aF*Hp0qx=D=fx*+&&t;ucLK6VEtQrCU diff --git a/images/bars_horizontal_grouped_stacked.png b/images/bars_horizontal_grouped_stacked.png index 6984e8ad97e9b15ec0f7cc18eeb7f658589736b0..e2ed02e67199d2d6e52e1ff42b4185bdabaf6016 100644 GIT binary patch delta 67 zcmexv`rUNHQYleGqYy&_D-&}oBQtFSLn{M=)9kT#C-0JyQN)nh74 delta 67 zcmexv`rUNHQYldblMq8oD`RsjV*_miLn{LVxhGe*PTnOYqlh7szcZJU0SG)@{an^L HB{Ts5&i@my diff --git a/images/bars_horizontal_grouped_stacked_100pct.png b/images/bars_horizontal_grouped_stacked_100pct.png index 050252e4329e57d2a9fbe5d3a49f389f1fa17020..a4a0263ed8da73e6fc1d790aabaaafa5f5757290 100644 GIT binary patch delta 67 zcmbQ~KGS`JyRxXEQHY^|m5I5Pk%hK_p_PHbYzwu9$uY_@iWoAlQs>z)0D-5gpUXO@ GgeCxEND<)x delta 67 zcmbQ~KGS`JyRxW(Nr<7Pm9e>%v9Y#+p_PGwKF@iL$uY_@iWo9+f#p*efWXt$&t;uc GLK6T{%n>yJ diff --git a/images/bars_horizontal_mekko.png b/images/bars_horizontal_mekko.png index 34453ebc5814b33a7131a092699702baaa3b27ae..64035ef20b09e4560096ffc7dfa100ec6b7ce316 100644 GIT binary patch delta 90 zcmbPlIp1=FuS}4kQHY^|m5I5Pv4OUMp_PF_N^M3Z0|SFRc?MtW?ChK ek%6JHu7Ra4h6YiE3rB$(7(8A5T-G@yGywo+{}?R* delta 90 zcmbPlIp1=FuS}4ENr<7Pm9e>%v8lF!p_PGw&z)aF3=9maC9V-ADTyViR=N2pnQ4^_ eMh1q)x(1fI7#eIFoz?+0FnGH9xvX%v8A?wp_PF_cg=*f$qL#siWoA#6g6%!0D-5gpUXO@ GgeCxx%o42t diff --git a/images/bars_matrix.png b/images/bars_matrix.png index 39568ba5ff0765b5900f56569edca63ade1c3f60..b23440a52ea5afdc0c034ed4d2174a90f9131029 100644 GIT binary patch delta 67 zcmbPhGuLLrX<1Q2qYy&_D-&}oV{>f-Ln{M=g}1`KPktaPqlh7Mq5YjV0}yz+`njxg HN@xNA%9<2~ delta 67 zcmbPhGuLLrX<1PNlMq8oD`Rsj6GLqSLn{M=O<~t1CO?psQN)lr%$>i40SG)@{an^L HB{Ts5p@b6+ diff --git a/images/bars_vertical.png b/images/bars_vertical.png index 714ebf0f4b05447a9c2a855e3575c388f6481e00..72685efb9c6725c4a28e8235a5cc90a2cbf6819e 100644 GIT binary patch delta 67 zcmZ3Qyfk@3u%W1-QHY^|m5I5Pv8A?wp_PHb$EUihCTAGRC}PMo?3gOX00f?{elF{r G5}E+7R1;SK delta 67 zcmZ3Qyfk@3u%W1dNr<7Pm9e>%iLthUp_PGw>d9kz7QHY^|m5I5PiJ`WEp_PHbiSt=kCOZhrC}PO$oOCIJ0SG)@{an^L HB{Ts5uW}Q; delta 67 zcmeyX_E&9#wy>yyNr<7Pm9e>%iK(`Mp_PGwo8)Ki$qvFYiWoBHF5cT2fWXt$&t;uc GLK6UI%n?@r diff --git a/images/bars_vertical_grouped_stacked.png b/images/bars_vertical_grouped_stacked.png index 81be6b0fd0598975f3c314f71610cc925985d56e..e3aaa3072a93e21f81000d61a53e5c5766661408 100644 GIT binary patch delta 67 zcmaEA`qXs8WGPWYqYy&_D-&}o6Ju=yLn{M=_<-ualUGQ|C}POy@g8<$00K`}KbLh* G2~7Z|EfSdk delta 67 zcmaEA`qXs8WGPVtlMq8oD`Rsj6ANtvLn{LV^>dR>PhKGTlMq8oD`RsjQv+=SLn{M=8{Kz|7#J8-OI#yLQW8s2t#b2IGSey< ej0_Bobqy?aF*K~ameL2*z~JfX=d#Wzp$P!`q8gb1 diff --git a/images/bars_vertical_mekko.png b/images/bars_vertical_mekko.png index 54182c52a995009a8872eac759428ffd0031adef..9ed1e400779778925969729b7c2acc5292ea8fe4 100644 GIT binary patch delta 90 zcmdmNyxDj|qGXVvQHY^|m5I5PiG{X-p_PF_Y`8u<0|SFRc?MtW?ChK ek%6JHu7Ra4h6Ww3>AQg%7(8A5T-G@yGywoXWEWBZ delta 90 zcmdmNyxDj|qGXVPNr<7Pm9e>%sgbsUp_PGwzsOl>1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=MXkrOSaD7(8A5T-G@yGywoyvlrF? diff --git a/images/bars_vertical_stacked.png b/images/bars_vertical_stacked.png index aba9f79b3f9de9dfd6a092f882bf674af94b79ed..c9f922efc6753b616fde47bcecc995a71f250f6c 100644 GIT binary patch delta 67 zcmZ2cxT0`Fn1!gJQHY^|m5I5Pse!hEp_PHbBB8{8ld~;k6ftBj``>h800K`}KbLh* G2~7Z{024m| delta 67 zcmZ2cxT0`Fn1!f;Nr<7Pm9e>%sfo6Mp_PGw&XFIAld~;k6ftCCXFAMf00K`}KbLh* G2~7Z@W)kH9 diff --git a/images/bars_vertical_stacked_100pct.png b/images/bars_vertical_stacked_100pct.png index d6153bd332101403bf2c14c6cee9b47aaba908f5..0251d096f280fe282935a826b8ee4f8cf3d08f17 100644 GIT binary patch delta 90 zcmaD-_oQxvkbRJ$QHY^|m5I5PsgbsUp_PGwX>Y-G1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=J+9PGkc$FnGH9xvX%shPHcp_PHb?&y~s3=9maC9V-ADTyViR=N2pnQ4^_ eMh1q)x(1fI7#dEo1#SUqVDNPHb6Mw<&;$V42pO9I diff --git a/images/bubbles_horizontal_grouped.png b/images/bubbles_horizontal_grouped.png index b848481e8b4dde293c0e0ffee9637abfa02ca16c..97b47e164cc82728a7503c5bd7943326d1020311 100644 GIT binary patch delta 90 zcmcbtaam&nqezgUQHY^|m5I5Psj0Ssp_PHb2Q9xy1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=NIt-<|?BFnGH9xvX%sfD(Ip_PHb>y$~M3=9maC9V-ADTyViR=N2pnQ4^_ eMh1q)x(1fI7#e0-YP|$%VDNPHb6Mw<&;$UBOc>Pw diff --git a/images/bubbles_horizontal_lollipop.png b/images/bubbles_horizontal_lollipop.png index b46cc9056b7f51a70d33c94c5a747cc41751343b..d6f1c253cb6dcce1fa84579c6b0c5f23b466bac9 100644 GIT binary patch delta 90 zcmca_bKhnIuUwF!QHY^|m5I5Pskyd+p_PF_`quaY1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=Lv0o}U3~VDNPHb6Mw<&;$US6&aTR delta 90 zcmca_bKhnIuUwFUNr<7Pm9d4Dfq}MxA&^W==)BFqz@S><8c~vxSdwa$o1c=IR>@#w cU}&ssV5y6tLBQ599jJl9)78&qol`;+0FTEQ0RR91 diff --git a/images/bubbles_matrix.png b/images/bubbles_matrix.png index 4c425956d56acaaf6404b6bda395212b2bf56f2f..c071d1f964a14af602b5e5cc095557842aaf053a 100644 GIT binary patch delta 67 zcmdn(x7%;SOEpnLqYy&_D-&}oQ%h|FLn{LV^DY(L$^X=36ftDnuFsjs00f?{elF{r G5}E+1wG%%8 delta 67 zcmdn(x7%;SOEpmglMq8oD`N{Q10!t%Ln{M=Z~td}n*2{qMiE1%eUXA60}yz+`njxg HN@xNA0-zN2 diff --git a/images/bubbles_standard.png b/images/bubbles_standard.png index 9ebb71c9078ea067bcc914d6a0d4975fb3609534..2011890aa82b8c382cae8f4193b083b99d156d07 100644 GIT binary patch delta 67 zcmdnvvBzV>D`inbqYy&_D-#PV14C^CLn{M=y?yqJC;wNLQN)lrbGcof0SG)@{an^L HB{Ts5(GL_a delta 67 zcmdnvvBzV>D`imwlMq8oD`N{Q0~2inLn{M=srqYAO#ZJdqlh8%q)4=c0SG)@{an^L HB{Ts5&(#y{ diff --git a/images/bubbles_vertical_grouped.png b/images/bubbles_vertical_grouped.png index d2e156b268fa098e0f279e4cd04d56f27a69fcbc..94e658f3542704f5401ed13eee138e246834daf8 100644 GIT binary patch delta 90 zcmZ3eu~1{fdEp>KqYy&_D-#PV17mFiLn{LVEkV651_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=Ppf)69Sx7(8A5T-G@yGywoj>lb7I delta 90 zcmZ3eu~1{fdEp=flMq8oD`N{Q19NQyLn{M=CC9giGB7ZxmbgZgq$HN4TIJ@aWTsUz e7#SEE>l#?J=GvXqYy&_D-#PV0}E{fLn{LV4Y8^M1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=NUqht2>sFnGH9xvXJ=GuslMq8oD`N{QLqlx?Ln{LV?!8SP85kH;OI#yLQW8s2t#b2IGSey< ej0_Bobqy?aF*H;?u<`|JVDNPHb6Mw<&;$V1=ozs9 diff --git a/images/lines_horizontal_grouped.png b/images/lines_horizontal_grouped.png index c8026995a45f89ff97c6cddb7d8d6425edafa3d7..a6dacb6eff6921fe1e1c78e9ed9ed8965f686440 100644 GIT binary patch delta 67 zcmdn%xz}?;wu-2sQHY^|m5GIwp@Fu6p_PHb^_NfQA8 diff --git a/images/lines_horizontal_grouped_stacked.png b/images/lines_horizontal_grouped_stacked.png index ffbc59d4199be70d5300017e068f1b9f702f58bd..988c7a25cc33b9703d9124b7e7577242c7226153 100644 GIT binary patch delta 67 zcmcZ}dp&kTtDdN#QHY^|m5GIwp^>(Mp_PGw!x_~DlV|D4C}PMMPMj;l00f?{elF{r G5}E+3xe~bm delta 67 zcmcZ}dp&kTtDdNVNr<7Pm9d4Dp{cfkp_PHb|4+*vO`fGEqlh8Xw_Yxe0SG)@{an^L HB{Ts5_bC*& diff --git a/images/lines_horizontal_stacked.png b/images/lines_horizontal_stacked.png index 9905b77f8ca0b3c181b4266129fcdf4343308254..b21818c66487588a1991362e68645939c0bfd8b1 100644 GIT binary patch delta 90 zcmbP~G%0Dr0fQhzqYy&_D-#PVLsM-7Ln{M=)@*4#1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=Ln*7Ec0dVDNPHb6Mw<&;$UHQyBmN delta 90 zcmbP~G%0Dr0fQg|lMq8oD`N{QLvw8dLn{M=jNI*~85kH;OI#yLQW8s2t#b2IGSey< ej0_Bobqy?aF*HaT@RtKMFnGH9xvXq>8AaQHY^|m5GIwp}Dq!p_PGw&=)TD$$2U=iWoAvq>8A4Nr<7Pm9d4Dp{2Hgp_PGwMbMdPlk-$$6ftBx`Af7IfWXt$&t;uc GLK6UY84-g3 diff --git a/images/lines_vertical_grouped.png b/images/lines_vertical_grouped.png index aae60d33e014af2c5318fa9371aa74cc770b456a..501dce34126d7e218053f097b67aaf119a25e312 100644 GIT binary patch delta 67 zcmeBm>vh|(TS?T=D8$gf%EZFT&{EsL(8|ET>2zV#vh|(TS?TwB*f6t%Gkon$WYtB(8|D|)u>fy@&zRsMGToQ`L^pAfWXt$&t;uc GLK6UmnG&r4 diff --git a/images/lines_vertical_grouped_stacked.png b/images/lines_vertical_grouped_stacked.png index 79ea6a81b86a3e0321d80fbe9de9ee3b842d9f24..e15bfb71249b8040384fd66f5e264167d4f0ef56 100644 GIT binary patch delta 90 zcmZ1&y)b%%ziyDBQHY^|m5GIwk&(86p_PHbIcFtj1_lPz64!{5l*E!$tK9sQ%(O}d eBLhQYT?0#93=R9`X8i|hVDNPHb6Mw<&;$T~Ul{BF delta 90 zcmZ1&y)b%%ziyC$Nr<7Pm9d4Dk+HUcp_PGw(Ap{13=9maC9V-ADTyViR=N2pnQ4^_ eMh1q)x(1fI7#fm4=(GbhFnGH9xvXRc?MtW?ChK ek%6JHu7Ra4h6d@pqZ@%57(8A5T-G@yGywofQ5W3+ delta 90 zcmbQ_J;{56gKCh0Nr<7Pm9d4Dv4OUMp_PHb`}cho3=9maC9V-ADTyViR=N2pnQ4^_ eMh1q)x(1fI7#iknGHU^9VDNPHb6Mw<&;$Uh^ciUY diff --git a/images/scatter_matrix.png b/images/scatter_matrix.png index 5051720c9a484e938d8ac39b10e0dd965be6d4a5..a517b7b80dc10b85e7f75b442e7441d9bdd5cd64 100644 GIT binary patch delta 67 zcmca^eBF3MtE8x*QHY^|m5GIwv7xqsp_PGwF{6dUf-Ln{M=fQ!6Ulh>-tC}PMc_G~@I00f?{elF{r G5}E+Fp%ZNY delta 67 zcmaFm|H^;E40TZhlMq8oD`N{F(l#)(GBCKqa3Fs2T6Gyk44JjD6^|K!z|+;wWt~$( F69CL%6cGRb