UNPKG

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").

872 lines (817 loc) 208 kB
// 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.char