diff --git a/lib/timeline/component/LineGraph.js b/lib/timeline/component/LineGraph.js index 9ea48a71..c9026e46 100644 --- a/lib/timeline/component/LineGraph.js +++ b/lib/timeline/component/LineGraph.js @@ -339,7 +339,11 @@ LineGraph.prototype.setGroups = function(groups) { }; - +/** + * Update the datapoints + * @param [ids] + * @private + */ LineGraph.prototype._onUpdate = function(ids) { this._updateUngrouped(); this._updateAllGroupData(); @@ -417,7 +421,8 @@ LineGraph.prototype._updateGroup = function (group, groupId) { LineGraph.prototype._updateAllGroupData = function () { if (this.itemsData != null) { var groupsContent = {}; - for (var groupId in this.groups) { + var groupId; + for (groupId in this.groups) { if (this.groups.hasOwnProperty(groupId)) { groupsContent[groupId] = []; } @@ -429,7 +434,7 @@ LineGraph.prototype._updateAllGroupData = function () { groupsContent[item.group].push(item); } } - for (var groupId in this.groups) { + for (groupId in this.groups) { if (this.groups.hasOwnProperty(groupId)) { this.groups[groupId].setItems(groupsContent[groupId]); } @@ -467,20 +472,6 @@ LineGraph.prototype._updateUngrouped = function() { } } - // much much slower -// var datapoints = this.itemsData.get({ -// filter: function (item) {return item.group === undefined;}, -// showInternalIds:true -// }); -// if (datapoints.length > 0) { -// var updateQuery = []; -// for (var i = 0; i < datapoints.length; i++) { -// updateQuery.push({id:datapoints[i].id, group: UNGROUPED}); -// } -// this.itemsData.update(updateQuery, true); -// } -// var t1 = new Date(); -// var pointInUNGROUPED = this.itemsData.get({filter: function (item) {return item.group == UNGROUPED;}}); if (ungroupedCounter == 0) { delete this.groups[UNGROUPED]; this.legendLeft.removeGroup(UNGROUPED); @@ -488,8 +479,6 @@ LineGraph.prototype._updateUngrouped = function() { this.yAxisLeft.removeGroup(UNGROUPED); this.yAxisRight.removeGroup(UNGROUPED); } -// console.log("getting amount ungrouped",new Date() - t1); -// console.log("putting in ungrouped",new Date() - t0); } else { delete this.groups[UNGROUPED]; @@ -618,17 +607,17 @@ LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, // what data we need to draw. Sorted data is much faster. // more optimization is possible by doing the sampling before and using the binary search // to find the end date to determine the increment. - var group; + var group, i, j, item; if (groupIds.length > 0) { - for (var i = 0; i < groupIds.length; i++) { + for (i = 0; i < groupIds.length; i++) { group = this.groups[groupIds[i]]; groupsData[groupIds[i]] = []; var dataContainer = groupsData[groupIds[i]]; // optimization for sorted data if (group.options.sort == true) { var guess = Math.max(0, util.binarySearchGeneric(group.itemsData, minDate, 'x', 'before')); - for (var j = guess; j < group.itemsData.length; j++) { - var item = group.itemsData[j]; + for (j = guess; j < group.itemsData.length; j++) { + item = group.itemsData[j]; if (item !== undefined) { if (item.x > maxDate) { dataContainer.push(item); @@ -641,8 +630,8 @@ LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, } } else { - for (var j = 0; j < group.itemsData.length; j++) { - var item = group.itemsData[j]; + for (j = 0; j < group.itemsData.length; j++) { + item = group.itemsData[j]; if (item !== undefined) { if (item.x > minDate && item.x < maxDate) { dataContainer.push(item); @@ -663,64 +652,70 @@ LineGraph.prototype._applySampling = function (groupIds, groupsData) { group = this.groups[groupIds[i]]; if (group.options.sampling == true) { var dataContainer = groupsData[groupIds[i]]; - var increment = 1; - var amountOfPoints = dataContainer.length; + if (dataContainer.length > 0) { + var increment = 1; + var amountOfPoints = dataContainer.length; - // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop - // of width changing of the yAxis. - var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x); - var pointsPerPixel = amountOfPoints / xDistance; - increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel))); + // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop + // of width changing of the yAxis. + var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x); + var pointsPerPixel = amountOfPoints / xDistance; + increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel))); - var sampledData = []; - for (var j = 0; j < amountOfPoints; j += increment) { - sampledData.push(dataContainer[j]); + var sampledData = []; + for (var j = 0; j < amountOfPoints; j += increment) { + sampledData.push(dataContainer[j]); + } + groupsData[groupIds[i]] = sampledData; } - groupsData[groupIds[i]] = sampledData; } } } }; LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) { - var groupData, group; + var groupData, group, i,j; var barCombinedDataLeft = []; var barCombinedDataRight = []; var barCombinedData; if (groupIds.length > 0) { - for (var i = 0; i < groupIds.length; i++) { + for (i = 0; i < groupIds.length; i++) { groupData = groupsData[groupIds[i]]; - group = this.groups[groupIds[i]]; - if (group.options.style == 'line' || group.options.barChart.handleOverlap != "stack") { - var yMin = groupData[0].y; - var yMax = groupData[0].y; - for (var j = 0; j < groupData.length; j++) { - yMin = yMin > groupData[j].y ? groupData[j].y : yMin; - yMax = yMax < groupData[j].y ? groupData[j].y : yMax; - } - groupRanges[groupIds[i]] = {min: yMin, max: yMax, yAxisOrientation: group.options.yAxisOrientation}; - } - else if (group.options.style == 'bar') { - if (group.options.yAxisOrientation == 'left') { - barCombinedData = barCombinedDataLeft; - } - else { - barCombinedData = barCombinedDataRight; + if (groupData.length > 0) { + group = this.groups[groupIds[i]]; + if (group.options.style == 'line' || group.options.barChart.handleOverlap != "stack") { + var yMin = groupData[0].y; + var yMax = groupData[0].y; + for (j = 0; j < groupData.length; j++) { + yMin = yMin > groupData[j].y ? groupData[j].y : yMin; + yMax = yMax < groupData[j].y ? groupData[j].y : yMax; + } + groupRanges[groupIds[i]] = {min: yMin, max: yMax, yAxisOrientation: group.options.yAxisOrientation}; } + else if (group.options.style == 'bar') { + if (group.options.yAxisOrientation == 'left') { + barCombinedData = barCombinedDataLeft; + } + else { + barCombinedData = barCombinedDataRight; + } - groupRanges[groupIds[i]] = {min: 0, max: 0, yAxisOrientation: group.options.yAxisOrientation, ignore: true}; + groupRanges[groupIds[i]] = {min: 0, max: 0, yAxisOrientation: group.options.yAxisOrientation, ignore: true}; - // combine data - for (var j = 0; j < groupData.length; j++) { - barCombinedData.push({ - x: groupData[j].x, - y: groupData[j].y, - groupId: groupIds[i] - }); + // combine data + for (j = 0; j < groupData.length; j++) { + barCombinedData.push({ + x: groupData[j].x, + y: groupData[j].y, + groupId: groupIds[i] + }); + } } } } + + var intersections; if (barCombinedDataLeft.length > 0) { // sort by time and by group barCombinedDataLeft.sort(function (a, b) { @@ -729,8 +724,8 @@ LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) { } else { return a.x - b.x; } - }) - var intersections = {}; + }); + intersections = {}; this._getDataIntersections(intersections, barCombinedDataLeft); groupRanges["__barchartLeft"] = this._getStackedBarYRange(intersections, barCombinedDataLeft); groupRanges["__barchartLeft"].yAxisOrientation = "left"; @@ -744,8 +739,8 @@ LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) { } else { return a.x - b.x; } - }) - var intersections = {}; + }); + intersections = {}; this._getDataIntersections(intersections, barCombinedDataRight); groupRanges["__barchartRight"] = this._getStackedBarYRange(intersections, barCombinedDataRight); groupRanges["__barchartRight"].yAxisOrientation = "right"; @@ -781,7 +776,8 @@ LineGraph.prototype._getStackedBarYRange = function (intersections, combinedData /** * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden. - * @param {array} groupIds + * @param {Array} groupIds + * @param {Object} groupRanges * @private */ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) { @@ -789,23 +785,24 @@ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) { var yAxisLeftUsed = false; var yAxisRightUsed = false; var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal; - // if groups are present if (groupIds.length > 0) { for (var i = 0; i < groupIds.length; i++) { - if (groupRanges[groupIds[i]].ignore !== true) { - minVal = groupRanges[groupIds[i]].min; - maxVal = groupRanges[groupIds[i]].max; - - if (groupRanges[groupIds[i]].yAxisOrientation == 'left') { - yAxisLeftUsed = true; - minLeft = minLeft > minVal ? minVal : minLeft; - maxLeft = maxLeft < maxVal ? maxVal : maxLeft; - } - else { - yAxisRightUsed = true; - minRight = minRight > minVal ? minVal : minRight; - maxRight = maxRight < maxVal ? maxVal : maxRight; + if (groupRanges.hasOwnProperty(groupIds[i])) { + if (groupRanges[groupIds[i]].ignore !== true) { + minVal = groupRanges[groupIds[i]].min; + maxVal = groupRanges[groupIds[i]].max; + + if (groupRanges[groupIds[i]].yAxisOrientation == 'left') { + yAxisLeftUsed = true; + minLeft = minLeft > minVal ? minVal : minLeft; + maxLeft = maxLeft < maxVal ? maxVal : maxLeft; + } + else { + yAxisRightUsed = true; + minRight = minRight > minVal ? minVal : minRight; + maxRight = maxRight < maxVal ? maxVal : maxRight; + } } } } @@ -883,14 +880,15 @@ LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) { /** * draw a bar graph - * @param datapoints - * @param group + * + * @param groupIds + * @param processedGroupData */ LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) { var combinedData = []; var intersections = {}; var coreDistance; - var key; + var key, drawData; var group; var i,j; var barPoints = 0; @@ -936,14 +934,14 @@ LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) { if (intersections[key] === undefined) { if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);} if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));} - var drawData = this._getSafeDrawData(coreDistance, group, minWidth); + drawData = this._getSafeDrawData(coreDistance, group, minWidth); } else { var nextKey = i + (intersections[key].amount - intersections[key].resolved); var prevKey = i - (intersections[key].resolved + 1); if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);} if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));} - var drawData = this._getSafeDrawData(coreDistance, group, minWidth); + drawData = this._getSafeDrawData(coreDistance, group, minWidth); intersections[key].resolved += 1; if (group.options.barChart.handleOverlap == 'stack') { @@ -953,8 +951,8 @@ LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) { else if (group.options.barChart.handleOverlap == 'sideBySide') { drawData.width = drawData.width / intersections[key].amount; drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1)); - if (group.options.barChart.align == 'left') {offset -= 0.5*drawData.width;} - else if (group.options.barChart.align == 'right') {offset += 0.5*drawData.width;} + if (group.options.barChart.align == 'left') {drawData.offset -= 0.5*drawData.width;} + else if (group.options.barChart.align == 'right') {drawData.offset += 0.5*drawData.width;} } } DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' bar', this.svgElements, this.svg); @@ -965,7 +963,12 @@ LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) { } }; - +/** + * Fill the intersections object with counters of how many datapoints share the same x coordinates + * @param intersections + * @param combinedData + * @private + */ LineGraph.prototype._getDataIntersections = function (intersections, combinedData) { // get intersections var coreDistance; @@ -985,10 +988,15 @@ LineGraph.prototype._getDataIntersections = function (intersections, combinedDat } }; -//LineGraph.prototype._accumulate = function (intersections, combinedData) { - - - +/** + * Get the width and offset for bargraphs based on the coredistance between datapoints + * + * @param coreDistance + * @param group + * @param minWidth + * @returns {{width: Number, offset: Number}} + * @private + */ LineGraph.prototype._getSafeDrawData = function (coreDistance, group, minWidth) { var width, offset; if (coreDistance < group.options.barChart.width && coreDistance > 0) { @@ -1003,7 +1011,7 @@ LineGraph.prototype._getSafeDrawData = function (coreDistance, group, minWidth) } } else { - // no collisions, plot with default settings + // default settings width = group.options.barChart.width; offset = 0; if (group.options.barChart.align == 'left') { @@ -1021,7 +1029,7 @@ LineGraph.prototype._getSafeDrawData = function (coreDistance, group, minWidth) /** * draw a line graph * - * @param datapoints + * @param dataset * @param group */ LineGraph.prototype._drawLineGraph = function (dataset, group) { @@ -1067,10 +1075,11 @@ LineGraph.prototype._drawLineGraph = function (dataset, group) { /** * draw the data points * - * @param dataset - * @param JSONcontainer - * @param svg - * @param group + * @param {Array} dataset + * @param {Object} JSONcontainer + * @param {Object} svg | SVG DOM element + * @param {GraphGroup} group + * @param {Number} [offset] */ LineGraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg, offset) { if (offset === undefined) {offset = 0;}