dimple-js
Version:
Dimple is an object-oriented API allowing you to create flexible axis-based charts using [d3.js](http://d3js.org "d3.js").
997 lines (941 loc) • 272 kB
JavaScript
// Copyright: 2015 AlignAlytics
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
// Source: /src/objects/begin.js
// Wrap all application code in a self-executing function which handles optional AMD/CommonJS publishing
(function (context, dimple) {
"use strict";
if (typeof exports === "object") {
// CommonJS
module.exports = dimple(require('d3'));
} else {
if (typeof define === "function" && define.amd) {
// RequireJS | AMD
define(["d3"], function (d3) {
// publish dimple to the global namespace for backwards compatibility
// and define it as an AMD module
context.dimple = dimple(d3);
return context.dimple;
});
} else {
// No AMD, expect d3 to exist in the current context and publish
// dimple to the global namespace
if (!context.d3) {
if (console && console.warn) {
console.warn("dimple requires d3 to run. Are you missing a reference to the d3 library?");
} else {
throw "dimple requires d3 to run. Are you missing a reference to the d3 library?";
}
} else {
context.dimple = dimple(context.d3);
}
}
}
}(this, function (d3) {
"use strict";
// Create the stub object
var dimple = {
version: "2.1.4",
plot: {},
aggregateMethod: {}
};
// Copyright: 2015 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, autoRotateLabel) {
// 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;
// Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-title
this.title = undefined;
// Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-clamp
this.clamp = true;
// Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-ticks
this.ticks = null;
// Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-fontSize
this.fontSize = "10px";
// Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-fontFamily
this.fontFamily = "sans-serif";
// Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#wiki-autoRotateLabel
this.autoRotateLabel = (autoRotateLabel === null || autoRotateLabel === undefined ? true : autoRotateLabel);
// If this is a composite axis, store links to all slaves
this._slaves = [];
// 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: 2015 AlignAlytics
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
// Source: /src/objects/axis/methods/_draw.js
this._draw = null;
// Copyright: 2015 AlignAlytics
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
// Source: /src/objects/axis/methods/_getAxisData.js
// Get all the datasets which may affect this axis
this._getAxisData = function () {
var i,
series,
returnData = [],
addChartData = false;
if (this.chart && this.chart.series) {
for (i = 0; i < this.chart.series.length; i += 1) {
series = this.chart.series[i];
// If the series is related to this axis
if (series[this.position] === this) {
// If the series has its own data set add it to the return array
if (series.data && series.data.length > 0) {
returnData = returnData.concat(series.data);
} else {
addChartData = true;
}
}
}
if (addChartData && this.chart.data) {
returnData = returnData.concat(this.chart.data);
}
}
return returnData;
};
// Copyright: 2015 AlignAlytics
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
// Source: /src/objects/axis/methods/_getFontSize.js
this._getFontSize = function () {
var fontSize;
if (!this.fontSize || this.fontSize.toString().toLowerCase() === "auto") {
fontSize = (this.chart._heightPixels() / 35 > 10 ? this.chart._heightPixels() / 35 : 10) + "px";
} else if (!isNaN(this.fontSize)) {
fontSize = this.fontSize + "px";
} else {
fontSize = this.fontSize;
}
return fontSize;
};
// Copyright: 2015 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 = -Math.floor(Math.log(this._tick_step) / Math.LN10);
returnFormat = d3.format(",." + dp + "f");
}
} else {
returnFormat = function (n) { return n; };
}
return returnFormat;
};
// Copyright: 2015 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) {
// 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: 2015 AlignAlytics
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
// Source: /src/objects/axis/methods/getTooltipText.js
this._getTooltipText = function (rows, d) {
if (this._hasTimeField()) {
if (d[this.position + "Field"][0]) {
rows.push(this.timeField + ": " + this._getFormat()(d[this.position + "Field"][0]));
}
} else if (this._hasCategories()) {
// Add the categories
this.categoryFields.forEach(function (c, i) {
if (c !== null && c !== undefined && d[this.position + "Field"][i]) {
// If the category name and value match don't display the category name
rows.push(c + (d[this.position + "Field"][i] !== c ? ": " + d[this.position + "Field"][i] : ""));
}
}, this);
} else if (this._hasMeasure()) {
switch (this.position) {
case "x":
rows.push(this.measure + ": " + this._getFormat()(d.width));
break;
case "y":
rows.push(this.measure + ": " + this._getFormat()(d.height));
break;
case "p":
rows.push(this.measure + ": " + this._getFormat()(d.angle) + " (" + (d3.format("%")(d.piePct)) + ")");
break;
default:
rows.push(this.measure + ": " + this._getFormat()(d[this.position + "Value"]));
break;
}
}
};
// Copyright: 2015 AlignAlytics
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
// Source: /src/objects/axis/methods/_getTopMaster.js
this._getTopMaster = function () {
// The highest level master
var topMaster = this;
if (this.master !== null && this.master !== undefined) {
topMaster = this.master._getTopMaster();
}
return topMaster;
};
// Copyright: 2015 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: 2015 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: 2015 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: 2015 AlignAlytics
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
// Source: /src/objects/axis/methods/_parseDate.js
this._parseDate = function (inDate) {
// A javascript date object
var outDate;
if (this.dateParseFormat === null || this.dateParseFormat === undefined) {
// Moved this into the condition so that using epoch time requires no data format to be set.
// For example 20131122 might be parsed as %Y%m%d not treated as epoch time.
if (!isNaN(inDate)) {
// If inDate is a number, assume it's epoch time
outDate = new Date(inDate);
} else {
// If nothing has been explicity defined you are in the hands of the browser gods
// may they smile upon you...
outDate = Date.parse(inDate);
}
} else {
outDate = d3.time.format(this.dateParseFormat).parse(inDate);
}
// Return the date
return outDate;
};
// Copyright: 2015 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,
tickCount = this.ticks || 10,
getOrderedCategories = function (self, axPos, oppPos) {
var category = self.categoryFields[0],
axisData = self._getAxisData(),
sortBy = category,
desc = false,
isDate = true,
currentValue = null,
i,
definitions;
// Check whether this field is a date
for (i = 0; i < axisData.length; i += 1) {
currentValue = self._parseDate(axisData[i][category]);
if (currentValue !== null && currentValue !== undefined && isNaN(currentValue)) {
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(axisData, 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" && (this._scale === null || refactor)) {
if (this._hasTimeField()) {
this._scale = d3.time.scale()
// Previously used rangeRound which causes problems with the area chart (Issue #79)
.range([this.chart._xPixels(), this.chart._xPixels() + this.chart._widthPixels()])
.domain([this._min, this._max])
.clamp(this.clamp);
} 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(this.clamp)
.base(this.logBase)
.nice();
} else if (this.measure === null || this.measure === undefined) {
distinctCats = getOrderedCategories(this, "x", "y");
// If there are any slaves process accordingly
if (this._slaves !== null && this._slaves !== undefined) {
this._slaves.forEach(function (slave) {
distinctCats = distinctCats.concat(getOrderedCategories(slave, "x", "y"));
}, this);
}
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])
.clamp(this.clamp)
.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);
if (this.ticks) {
this._draw.ticks(tickCount);
}
break;
case 1:
this._draw = d3.svg.axis()
.orient("top")
.scale(this._scale);
if (this.ticks) {
this._draw.ticks(tickCount);
}
break;
default:
break;
}
}
} else if (this.position === "y" && (this._scale === null || refactor)) {
if (this._hasTimeField()) {
this._scale = d3.time.scale()
// Previously used rangeRound which causes problems with the area chart (Issue #79)
.range([this.chart._yPixels() + this.chart._heightPixels(), this.chart._yPixels()])
.domain([this._min, this._max])
.clamp(this.clamp);
} 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(this.clamp)
.base(this.logBase)
.nice();
} else if (this.measure === null || this.measure === undefined) {
distinctCats = getOrderedCategories(this, "y", "x");
// If there are any slaves process accordingly
if (this._slaves !== null && this._slaves !== undefined) {
this._slaves.forEach(function (slave) {
distinctCats = distinctCats.concat(getOrderedCategories(slave, "y", "x"));
}, this);
}
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])
.clamp(this.clamp)
.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);
if (this.ticks) {
this._draw.ticks(tickCount);
}
break;
case 1:
this._draw = d3.svg.axis()
.orient("right")
.scale(this._scale);
if (this.ticks) {
this._draw.ticks(tickCount);
}
break;
default:
break;
}
}
} else if (this.position.length > 0 && this.position[0] === "z" && this._scale === null) {
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(this.clamp)
.base(this.logBase);
} else {
this._scale = d3.scale.linear()
.range([1, this.chart._heightPixels() / 10])
.domain([this._min, this._max])
.clamp(this.clamp);
}
} else if (this.position.length > 0 && this.position[0] === "p" && this._scale === null) {
if (this.useLog) {
this._scale = d3.scale.log()
.range([0, 360])
.domain([
(this._min === 0 ? Math.pow(this.logBase, -1) : this._min),
(this._max === 0 ? -1 * Math.pow(this.logBase, -1) : this._max)
])
.clamp(this.clamp)
.base(this.logBase);
} else {
this._scale = d3.scale.linear()
.range([0, 360])
.domain([this._min, this._max])
.clamp(this.clamp);
}
} else if (this.position.length > 0 && this.position[0] === "c" && this._scale === null) {
this._scale = d3.scale.linear()
.range([0, (this.colors === null || this.colors.length === 1 ? 1 : this.colors.length - 1)])
.domain([this._min, this._max])
.clamp(this.clamp);
}
// Apply this scale to all slaves as well
if (this._slaves !== null && this._slaves !== undefined && this._slaves.length > 0) {
this._slaves.forEach(function (slave) {
slave._scale = this._scale;
}, this);
}
// 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(tickCount).length > 0 && (this.position === "x" || this.position === "y")) {
// Get the ticks determined based on the specified split
ticks = this._scale.ticks(tickCount);
// Get the step between ticks
step = ticks[1] - ticks[0];
// Get the remainder
remainder = ((this._max - this._min) % step).toFixed(0);
// Store the tick step if needed to calculate _getFormat.
this._tick_step = step;
// If the remainder is not zero
if (remainder !== 0) {
// Set the bounds
this._max = Math.ceil(this._max / step) * step;
this._min = Math.floor(this._min / step) * step;
// Recursively call the method to recalculate the scale. This shouldn't enter this block again.
this._update(true);
}
}
// Populate the origin. Previously this incorrectly looked up 0 on the axis which only works
// for measure axes leading to Issue #19. This fix uses the first category value in cases where
// one is required.
if (distinctCats !== null && distinctCats !== undefined && distinctCats.length > 0) {
origin = this._scale.copy()(distinctCats[0]);
} else if (this._min > 0) {
origin = this._scale.copy()(this._min);
} else if (this._max < 0) {
origin = this._scale.copy()(this._max);
} else {
origin = this._scale.copy()(0);
}
if (this._origin !== origin) {
this._previousOrigin = (this._origin === null ? origin : this._origin);
this._origin = origin;
}
// Return axis for chaining
return this;
};
// Copyright: 2015 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: 2015 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: 2015 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;
// Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-ease
this.ease = "cubic-in-out";
// Help: http://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.chart#wiki-staggerDraw
this.staggerDraw = false;
// 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 = {};
// Classes assigned to series values
this._assignedClasses = {};
// The next colour index to use, this value is cycled around for all default colours
this._nextColor = 0;
// The next series class index to use, this value is cycled around for all default classes
this._nextClass = 0;
// Copyright: 2015 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: 2015 AlignAlytics
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
// Source: /src/objects/chart/methods/_getAllData.js
// Mash together all of the datasets
this._getAllData = function () {
// The return array will include all data for chart as well as any series
var returnData = [];
// If there is data at the chart level
if (this.data !== null && this.data !== undefined && this.data.length > 0) {
returnData = returnData.concat(this.data);
}
// If there are series defined
if (this.series !== null && this.series !== undefined && this.series.length > 0) {
this.series.forEach(function (s) {
if (s.data !== null && s.data !== undefined && s.data.length > 0) {
returnData = returnData.concat(s.data);
}
});
}
// Return the final dataset
return returnData;
};
// Copyright: 2015 AlignAlytics
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
// Source: /src/objects/chart/methods/_getData.js
// Create a dataset containing positioning information for every series
this._getData = function (data, cats, agg, order, stacked, x, y, z, p, c) {
// The data for this series
var returnData = [],
// Handle multiple category values by iterating the fields in the row and concatenate 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, p: 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: [], p: [] },
colorBounds = { min: null, max: null },
tot,
running = { x: [], y: [], z: [], p: [] },
addedCats = [],
catTotals = {},
grandTotals = { x: 0, y: 0, z: 0, p: 0 },
key,
storyCat = "",
orderedStoryboardArray = [],
seriesCat = [],
orderedSeriesArray = [],
xCat = "",
xSortArray = [],
yCat = "",
ySortArray = [],
pCat = "",
pSortArray = [],
rules = [],
sortedData = data,
groupRules = [];
if (this.storyboard && this.storyboard.categoryFields.length > 0) {
storyCat = this.storyboard.categoryFields[0];
orderedStoryboardArray = dimple._getOrderedList(sortedData, storyCat, this.storyboard._orderRules);
}
// Deal with mekkos
if (x && x._hasCategories() && x._hasMeasure()) {
xCat = x.categoryFields[0];
xSortArray = dimple._getOrderedList(sortedData, xCat, x._orderRules.concat([{ ordering : x.measure, desc : true }]));
}
if (y && y._hasCategories() && y._hasMeasure()) {
yCat = y.categoryFields[0];
ySortArray = dimple._getOrderedList(sortedData, yCat, y._orderRules.concat([{ ordering : y.measure, desc : true }]));
}
if (p && p._hasCategories() && p._hasMeasure()) {
pCat = p.categoryFields[0];
pSortArray = dimple._getOrderedList(sortedData, pCat, p._orderRules.concat([{ ordering : p.measure, desc : true }]));
}
if (sortedData.length > 0 && cats && cats.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(order);
seriesCat = [];
cats.forEach(function (cat) {
if (sortedData[0][cat] !== undefined) {
seriesCat.push(cat);
}
}, this);
if (p && p._hasMeasure()) {
rules.push({ ordering : p.measure, desc : true });
} else if (c && c._hasMeasure()) {
rules.push({ ordering : c.measure, desc : true });
} else if (z && z._hasMeasure()) {
rules.push({ ordering : z.measure, desc : true });
} else if (x && x._hasMeasure()) {
rules.push({ ordering : x.measure, desc : true });
} else if (y && y._hasMeasure()) {
rules.push({ ordering : y.measure, desc : true });
}
orderedSeriesArray = dimple._getOrderedList(sortedData, seriesCat, rules);
}
sortedData.sort(function (a, b) {
var returnValue = 0,
categories,
comp,
p,
q,
aMatch,
bMatch;
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 (pCat !== "" && returnValue === 0) {
returnValue = pSortArray.indexOf(a[pCat]) - ySortArray.indexOf(b[pCat]);
}
if (seriesCat && seriesCat.length > 0 && returnValue === 0) {
categories = [].concat(seriesCat);
returnValue = 0;
for (p = 0; p < orderedSeriesArray.length; p += 1) {
comp = [].concat(orderedSeriesArray[p]);
aMatch = true;
bMatch = true;
for (q = 0; q < categories.length; q += 1) {
aMatch = aMatch && (a[categories[q]] === comp[q]);
bMatch = bMatch && (b[categories[q]] === comp[q]);
}
if (aMatch && bMatch) {
returnValue = 0;
break;
} else if (aMatch) {
returnValue = -1;
break;
} else if (bMatch) {
returnValue = 1;
break;
}
}
}
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(x, d),
yField = getField(y, d),
zField = getField(z, d),
pField = getField(p, d),
// Get the aggregate field using the other fields if necessary
aggField = [],
key,
k,
i,
newRow,
updateData;
if (!cats || cats.length === 0) {
aggField = ["All"];
} else {
// Iterate the category fields
for (i = 0; i < cats.length; i += 1) {
// Either add the value of the field or the name itself. This allows users to add custom values, for example
// Setting a particular color for a set of values can be done by using a non-existent final value and then coloring
// by it
if (d[cats[i]] === undefined) {
aggField.push(cats[i]);
} else {
aggField.push(d[cats[i]]);
}
}
}
// Add a key
key = aggField.join("/") + "_" + xField.join("/") + "_" + yField.join("/") + "_" + pField.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,
pField: pField,
pValue: null,
pCount: 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: [],
pValueList: [],
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() && d[axis.measure] !== null && d[axis.measure] !== undefined) {
// 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"] = agg(lhs, rhs);
retRow[axis.position + "Count"] += 1;
}
}
}
};
// Update all the axes
updateData(x, this.storyboard);
updateData(y, this.storyboard);
updateData(z, this.storyboard);
updateData(p, this.storyboard);
updateData(c, this.storyboard);
}, this);
// Get secondary elements if necessary
if (x && x._hasCategories() && x.categoryFields.length >