|
|
@ -4,8 +4,8 @@ |
|
|
|
* |
|
|
|
* A dynamic, browser-based visualization library. |
|
|
|
* |
|
|
|
* @version @@version |
|
|
|
* @date @@date |
|
|
|
* @version 1.1.0 |
|
|
|
* @date 2014-06-18 |
|
|
|
* |
|
|
|
* @license |
|
|
|
* Copyright (C) 2011-2014 Almende B.V, http://almende.com
|
|
|
@ -2558,10 +2558,199 @@ DataView.prototype._trigger = DataSet.prototype._trigger; |
|
|
|
DataView.prototype.subscribe = DataView.prototype.on; |
|
|
|
DataView.prototype.unsubscribe = DataView.prototype.off; |
|
|
|
|
|
|
|
/** |
|
|
|
* @constructor Group |
|
|
|
* @param {Number | String} groupId |
|
|
|
* @param {Object} data |
|
|
|
* @param {ItemSet} itemSet |
|
|
|
*/ |
|
|
|
function GraphGroup (group, options, linegraph) { |
|
|
|
this.options = util.copyObject(options,{}); |
|
|
|
this.linegraph = linegraph; |
|
|
|
this.usingDefaultStyle = group.className === undefined; |
|
|
|
this.update(group); |
|
|
|
if (this.usingDefaultStyle == true) { |
|
|
|
this.linegraph.groupsUsingDefaultStyles += 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
GraphGroup.prototype.setClass = function (className) { |
|
|
|
this.className = className; |
|
|
|
} |
|
|
|
|
|
|
|
GraphGroup.prototype.setOptions = function(options) { |
|
|
|
if (options !== undefined) { |
|
|
|
var fields = ['yAxisOrientation']; |
|
|
|
util.selectiveExtend(fields, options, this.options); |
|
|
|
this.linegraph._mergeOptions(this.options, options,'catmullRom'); |
|
|
|
this.linegraph._mergeOptions(this.options, options,'drawPoints'); |
|
|
|
this.linegraph._mergeOptions(this.options, options,'shaded'); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
GraphGroup.prototype.update = function(group) { |
|
|
|
this.group = group; |
|
|
|
this.content = group.content || 'graph'; |
|
|
|
this.className = group.className || this.className || "graphGroup" + this.linegraph.groupsUsingDefaultStyles; |
|
|
|
this.setOptions(group.options); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Created by Alex on 6/17/14. |
|
|
|
*/ |
|
|
|
function Legend(body, options, linegraph) { |
|
|
|
this.body = body; |
|
|
|
this.linegraph = linegraph; |
|
|
|
this.defaultOptions = { |
|
|
|
orientation: 'left' // left, right
|
|
|
|
} |
|
|
|
|
|
|
|
this.options = util.extend({},this.defaultOptions); |
|
|
|
|
|
|
|
this.svgElements = {}; |
|
|
|
this.dom = {}; |
|
|
|
this.classes; |
|
|
|
this.graphs = {}; |
|
|
|
|
|
|
|
this.setOptions(options); |
|
|
|
this.create(); |
|
|
|
}; |
|
|
|
|
|
|
|
Legend.prototype = new Component(); |
|
|
|
|
|
|
|
|
|
|
|
Legend.prototype.addGroup = function(label, graphOptions) { |
|
|
|
if (!this.graphs.hasOwnProperty(label)) { |
|
|
|
this.graphs[label] = graphOptions; |
|
|
|
this.redraw(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Legend.prototype.updateGroup = function(label, graphOptions) { |
|
|
|
this.graphs[label] = graphOptions; |
|
|
|
}; |
|
|
|
|
|
|
|
Legend.prototype.deleteGroup = function(label) { |
|
|
|
if (this.graphs.hasOwnProperty(label)) { |
|
|
|
delete this.graphs[label]; |
|
|
|
this.redraw(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Legend.prototype.create = function() { |
|
|
|
var frame = document.createElement('div'); |
|
|
|
frame.className = 'legend'; |
|
|
|
frame['legend'] = this; |
|
|
|
this.dom.frame = frame; |
|
|
|
|
|
|
|
this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg"); |
|
|
|
this.svg.style.position = "absolute"; |
|
|
|
this.svg.style.top = "10px"; |
|
|
|
this.svg.style.height = "300px"; |
|
|
|
this.svg.style.width = "300px"; |
|
|
|
this.svg.style.display = "block"; |
|
|
|
|
|
|
|
this.dom.frame.appendChild(this.svg); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Hide the component from the DOM |
|
|
|
*/ |
|
|
|
Legend.prototype.hide = function() { |
|
|
|
// remove the frame containing the items
|
|
|
|
if (this.dom.frame.parentNode) { |
|
|
|
this.dom.frame.parentNode.removeChild(this.dom.frame); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Show the component in the DOM (when not already visible). |
|
|
|
* @return {Boolean} changed |
|
|
|
*/ |
|
|
|
Legend.prototype.show = function() { |
|
|
|
// show frame containing the items
|
|
|
|
if (!this.dom.frame.parentNode) { |
|
|
|
this.body.dom.center.appendChild(this.dom.frame); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Legend.prototype.setOptions = function(options) { |
|
|
|
var fields = ['orientation']; |
|
|
|
util.selectiveExtend(fields, this.options, options); |
|
|
|
} |
|
|
|
|
|
|
|
Legend.prototype.redraw = function() { |
|
|
|
if (this.options.orientation == 'left') { |
|
|
|
this.svg.style.left = '10px'; |
|
|
|
} |
|
|
|
else { |
|
|
|
this.svg.style.right = '10px'; |
|
|
|
} |
|
|
|
console.log(this.graphs); |
|
|
|
// this.drawLegend();
|
|
|
|
} |
|
|
|
|
|
|
|
Legend.prototype.drawLegend = function() { |
|
|
|
this.linegraph._prepareSVGElements.call(this,this.svgElements); |
|
|
|
var x = 0; |
|
|
|
var y = 0; |
|
|
|
var lineLength = 30; |
|
|
|
var fillHeight = 10; |
|
|
|
var spacing = 25; |
|
|
|
var path, fillPath, outline; |
|
|
|
var legendWidth = 298; |
|
|
|
var padding = 5; |
|
|
|
|
|
|
|
var border = this._getSVGElement("rect", this.svgLegendElements, this.svgLegend); |
|
|
|
border.setAttributeNS(null, "x", x); |
|
|
|
border.setAttributeNS(null, "y", y); |
|
|
|
border.setAttributeNS(null, "width", legendWidth); |
|
|
|
border.setAttributeNS(null, "height", y + padding + classes.length * spacing); |
|
|
|
border.setAttributeNS(null, "class", "legendBackground"); |
|
|
|
x += 5; |
|
|
|
y += fillHeight + padding; |
|
|
|
|
|
|
|
if (classes.length > 0) { |
|
|
|
for (var i = 0; i < classes.length; i++) { |
|
|
|
outline = this._getSVGElement("rect", this.svgLegendElements, this.svgLegend); |
|
|
|
outline.setAttributeNS(null, "x", x); |
|
|
|
outline.setAttributeNS(null, "y", y - fillHeight); |
|
|
|
outline.setAttributeNS(null, "width", lineLength); |
|
|
|
outline.setAttributeNS(null, "height", 2*fillHeight); |
|
|
|
outline.setAttributeNS(null, "class", "outline"); |
|
|
|
|
|
|
|
path = this._getSVGElement("path", this.svgLegendElements, this.svgLegend); |
|
|
|
path.setAttributeNS(null, "class", classes[i]); |
|
|
|
path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + lineLength) + ","+y+""); |
|
|
|
if (this.options.shaded.enabled == true) { |
|
|
|
fillPath = this._getSVGElement("path", this.svgLegendElements, this.svgLegend); |
|
|
|
if (this.options.shaded.orientation == 'top') { |
|
|
|
fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) + |
|
|
|
"L"+x+","+y+" L"+ (x + lineLength) + ","+y+" L"+ (x + lineLength) + "," + (y - fillHeight)); |
|
|
|
} |
|
|
|
else { |
|
|
|
fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " + |
|
|
|
"L"+x+"," + (y + fillHeight) + " " + |
|
|
|
"L"+ (x + lineLength) + "," + (y + fillHeight) + |
|
|
|
"L"+ (x + lineLength) + ","+y); |
|
|
|
} |
|
|
|
fillPath.setAttributeNS(null, "class", classes[i] + " fill"); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.options.drawPoints.enabled == true) { |
|
|
|
this.drawPoint(x + 0.5 * lineLength,y,classes[i], this.svgLegendElements, this.svgLegend); |
|
|
|
} |
|
|
|
y += spacing; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
//TODO: bars
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this._cleanupSVGElements(this.svgLegendElements); |
|
|
|
} |
|
|
|
/** |
|
|
|
* A horizontal time axis |
|
|
|
* @param {Object} [options] See DataAxis.setOptions for the available |
|
|
@ -2597,13 +2786,6 @@ function DataAxis (body, options) { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// this.yRange = new Range(body,{
|
|
|
|
// direction: 'vertical',
|
|
|
|
// min: null,
|
|
|
|
// max: null,
|
|
|
|
// zoomMin: 1e-5,
|
|
|
|
// zoomMax: 1e9
|
|
|
|
// });
|
|
|
|
this.yRange = {start:0, end:0}; |
|
|
|
|
|
|
|
this.options = util.extend({}, this.defaultOptions); |
|
|
@ -3034,7 +3216,7 @@ DataAxis.prototype.snap = function(date) { |
|
|
|
return this.step.snap(date); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
|
|
|
|
|
|
|
|
function Linegraph(body, options) { |
|
|
|
this.id = util.randomUUID(); |
|
|
@ -3067,6 +3249,7 @@ function Linegraph(body, options) { |
|
|
|
this.dom = {}; |
|
|
|
this.props = {}; |
|
|
|
this.hammer = null; |
|
|
|
this.groups = {}; |
|
|
|
|
|
|
|
var me = this; |
|
|
|
this.itemsData = null; // DataSet
|
|
|
@ -3104,8 +3287,8 @@ function Linegraph(body, options) { |
|
|
|
this.touchParams = {}; // stores properties while dragging
|
|
|
|
|
|
|
|
this.svgElements = {}; |
|
|
|
this.svgLegendElements = {}; |
|
|
|
this.setOptions(options); |
|
|
|
this.groupsUsingDefaultStyles = 0; |
|
|
|
|
|
|
|
|
|
|
|
var me = this; |
|
|
@ -3149,15 +3332,6 @@ Linegraph.prototype._create = function(){ |
|
|
|
this.svg.style.display = "block"; |
|
|
|
frame.appendChild(this.svg); |
|
|
|
|
|
|
|
this.svgLegend = document.createElementNS('http://www.w3.org/2000/svg',"svg"); |
|
|
|
this.svgLegend.style.position = "absolute"; |
|
|
|
this.svgLegend.style.top = "10px"; |
|
|
|
this.svgLegend.style.height = "300px"; |
|
|
|
this.svgLegend.style.width = "300px"; |
|
|
|
this.svgLegend.style.display = "block"; |
|
|
|
|
|
|
|
frame.appendChild(this.svgLegend); |
|
|
|
|
|
|
|
// panel with time axis
|
|
|
|
this.yAxisLeft = new DataAxis(this.body, { |
|
|
|
orientation: 'left', |
|
|
@ -3179,9 +3353,17 @@ Linegraph.prototype._create = function(){ |
|
|
|
height: this.svg.style.height |
|
|
|
}); |
|
|
|
|
|
|
|
this.legend = new Legend(this.body, { |
|
|
|
orientation:'left' |
|
|
|
}); |
|
|
|
|
|
|
|
this.show(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* set the options of the linegraph. the mergeOptions is used for subObjects that have an enabled element. |
|
|
|
* @param options |
|
|
|
*/ |
|
|
|
Linegraph.prototype.setOptions = function(options) { |
|
|
|
if (options) { |
|
|
|
var fields = ['yAxisOrientation']; |
|
|
@ -3209,6 +3391,15 @@ Linegraph.prototype.setOptions = function(options) { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* this is used to set the options of subobjects in the options object. A requirement of these subobjects |
|
|
|
* is that they have an 'enabled' element which is optional for the user but mandatory for the program. |
|
|
|
* |
|
|
|
* @param [object] mergeTarget | this is either this.options or the options used for the groups. |
|
|
|
* @param [object] options | options |
|
|
|
* @param [String] option | this is the option key in the options argument |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._mergeOptions = function (mergeTarget, options,option) { |
|
|
|
if (options[option]) { |
|
|
|
if (typeof options[option] == 'boolean') { |
|
|
@ -3344,38 +3535,76 @@ Linegraph.prototype.setGroups = function(groups) { |
|
|
|
|
|
|
|
|
|
|
|
Linegraph.prototype._onUpdate = function(ids) { |
|
|
|
this._updateUngrouped(); |
|
|
|
this.updateGraph(); |
|
|
|
this.redraw(); |
|
|
|
}; |
|
|
|
Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate; |
|
|
|
Linegraph.prototype._onRemove = Linegraph.prototype._onUpdate; |
|
|
|
Linegraph.prototype._onUpdateGroups = Linegraph.prototype._onUpdate; |
|
|
|
Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdate; |
|
|
|
Linegraph.prototype._onRemoveGroups = Linegraph.prototype._onUpdate; |
|
|
|
Linegraph.prototype._onUpdateGroups = function (groupIds) { |
|
|
|
for (var i = 0; i < groupIds.length; i++) { |
|
|
|
var group = this.groupsData.get(groupIds[i]); |
|
|
|
if (!this.groups.hasOwnProperty(groupIds[i])) { |
|
|
|
this.groups[groupIds[i]] = new GraphGroup(group, this.options, this); |
|
|
|
this.legend.addGroup(groupIds[i],this.groups[groupIds[i]]); |
|
|
|
} |
|
|
|
else { |
|
|
|
this.groups[groupIds[i]].update(group); |
|
|
|
this.legend.updateGroup(groupIds[i],this.groups[groupIds[i]]); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this.updateGraph(); |
|
|
|
this.redraw(); |
|
|
|
}; |
|
|
|
Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdateGroups; |
|
|
|
|
|
|
|
Linegraph.prototype._onRemoveGroups = function (groupIds) { |
|
|
|
for (var i = 0; i < groupIds.length; i++) { |
|
|
|
this.legend.removeGroup(groupIds[i]); |
|
|
|
} |
|
|
|
this.updateGraph(); |
|
|
|
this.redraw(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Create or delete the group holding all ungrouped items. This group is used when |
|
|
|
* there are no groups specified. |
|
|
|
* there are no groups specified. This anonymous group is called 'graph'. |
|
|
|
* @protected |
|
|
|
*/ |
|
|
|
Linegraph.prototype._updateUngrouped = function() { |
|
|
|
var group = {content: "graph"}; |
|
|
|
if (!this.groups.hasOwnProperty(UNGROUPED)) { |
|
|
|
this.groups[UNGROUPED] = new GraphGroup(group, this.options, this); |
|
|
|
} |
|
|
|
else { |
|
|
|
this.groups[UNGROUPED].update(group); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.itemsData != null) { |
|
|
|
var datapoints = this.itemsData.get({ |
|
|
|
filter: function (item) {return item.group === undefined;}, |
|
|
|
showInternalIds:true |
|
|
|
}); |
|
|
|
|
|
|
|
var updateQuery = []; |
|
|
|
for (var i = 0; i < datapoints.length; i++) { |
|
|
|
updateQuery.push({id:datapoints[i].id, group: 'graph'}); |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
this.itemsData.update(updateQuery); |
|
|
|
|
|
|
|
var pointInUNGROUPED = this.itemsData.get({filter: function (item) {return item.group == UNGROUPED;}}); |
|
|
|
if (pointInUNGROUPED.length == 0) { |
|
|
|
this.legend.deleteGroup(UNGROUPED); |
|
|
|
delete this.groups[UNGROUPED]; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Repaint the component |
|
|
|
* Redraw the component, mandatory function |
|
|
|
* @return {boolean} Returns true if the component is resized |
|
|
|
*/ |
|
|
|
Linegraph.prototype.redraw = function() { |
|
|
@ -3384,6 +3613,8 @@ Linegraph.prototype.redraw = function() { |
|
|
|
if (this.lastWidth === undefined && this.width || this.lastWidth != this.width) { |
|
|
|
resized = true; |
|
|
|
} |
|
|
|
// check if this component is resized
|
|
|
|
resized = this._isResized() || resized; |
|
|
|
// check whether zoomed (in that case we need to re-stack everything)
|
|
|
|
var visibleInterval = this.body.range.end - this.body.range.start; |
|
|
|
var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.width != this.lastWidth); |
|
|
@ -3392,20 +3623,23 @@ Linegraph.prototype.redraw = function() { |
|
|
|
|
|
|
|
// calculate actual size and position
|
|
|
|
this.width = this.dom.frame.offsetWidth; |
|
|
|
this.svgLegend.style.left = (this.width - this.svgLegend.offsetWidth - 10) + "px"; |
|
|
|
|
|
|
|
// check if this component is resized
|
|
|
|
resized = this._isResized() || resized; |
|
|
|
if (resized) { |
|
|
|
// the svg element is three times as big as the width, this allows for fully dragging left and right
|
|
|
|
// without reloading the graph. the controls for this are bound to events in the constructor
|
|
|
|
if (resized == true) { |
|
|
|
this.svg.style.width = util.option.asSize(3*this.width); |
|
|
|
this.svg.style.left = util.option.asSize(-this.width); |
|
|
|
} |
|
|
|
if (zoomed) { |
|
|
|
if (zoomed == true) { |
|
|
|
this.updateGraph(); |
|
|
|
} |
|
|
|
return resized; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Update and redraw the graph. |
|
|
|
* |
|
|
|
*/ |
|
|
|
Linegraph.prototype.updateGraph = function () { |
|
|
|
// reset the svg elements
|
|
|
|
this._prepareSVGElements(this.svgElements); |
|
|
@ -3414,37 +3648,38 @@ Linegraph.prototype.updateGraph = function () { |
|
|
|
// look at different lines
|
|
|
|
var groupIds = this.itemsData.distinct('group'); |
|
|
|
if (groupIds.length > 0) { |
|
|
|
this._setYRanges(groupIds); |
|
|
|
this._updateYAxis(groupIds); |
|
|
|
for (var i = 0; i < groupIds.length; i++) { |
|
|
|
this.drawGraph(groupIds[i], i, groupIds.length); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
this._setYRanges(groupIds); |
|
|
|
this.drawGraph('graph', 0, 1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// this.legend.redraw();
|
|
|
|
|
|
|
|
// cleanup unused svg elements
|
|
|
|
this._cleanupSVGElements(this.svgElements); |
|
|
|
}; |
|
|
|
|
|
|
|
Linegraph.prototype._setYRanges = function(groupIds) { |
|
|
|
/** |
|
|
|
* this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden. |
|
|
|
* @param {array} groupIds |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._updateYAxis = function(groupIds) { |
|
|
|
var yAxisLeftUsed = false; |
|
|
|
var yAxisRightUsed = false; |
|
|
|
var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal; |
|
|
|
var orientation = 'left'; |
|
|
|
|
|
|
|
|
|
|
|
// if groups are present
|
|
|
|
if (groupIds.length > 0) { |
|
|
|
for (var i = 0; i < groupIds.length; i++) { |
|
|
|
orientation = 'left'; |
|
|
|
var group = this.groupsData.get(groupIds[i]); |
|
|
|
if (group != null) { |
|
|
|
if (group.options) { |
|
|
|
if (group.options.yAxisOrientation == 'right') { |
|
|
|
orientation = 'right'; |
|
|
|
} |
|
|
|
} |
|
|
|
var group = this.groups[groupIds[i]]; |
|
|
|
if (group.options.yAxisOrientation == 'right') { |
|
|
|
orientation = 'right'; |
|
|
|
} |
|
|
|
|
|
|
|
var view = new vis.DataSet(this.itemsData.get({filter: function (item) {return item.group == groupIds[i];}})); |
|
|
@ -3471,17 +3706,7 @@ Linegraph.prototype._setYRanges = function(groupIds) { |
|
|
|
this.yAxisRight.setRange({start: minRight, end: maxRight}); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
var yRange = {start: this.itemsData.min('y').y, end: this.itemsData.max('y').y}; |
|
|
|
if (this.options.yAxisOrientation == 'left') { |
|
|
|
this.yAxisLeft.setRange(yRange); |
|
|
|
yAxisLeftUsed = true; |
|
|
|
} |
|
|
|
else { |
|
|
|
this.yAxisRight.setRange(yRange); |
|
|
|
yAxisRightUsed = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var changed = this._toggleAxisVisiblity(yAxisLeftUsed, this.yAxisLeft); |
|
|
|
changed = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || changed; |
|
|
|
if (changed) { |
|
|
@ -3503,6 +3728,14 @@ Linegraph.prototype._setYRanges = function(groupIds) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function |
|
|
|
* |
|
|
|
* @param {boolean} axisUsed |
|
|
|
* @param {DataAxis object} axis |
|
|
|
* @returns {boolean} |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) { |
|
|
|
var changed = false; |
|
|
|
if (axisUsed == false) { |
|
|
@ -3520,17 +3753,35 @@ Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) { |
|
|
|
return changed; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* determine if the graph is a bar or line, get the group options and the datapoints. Then draw the graph. |
|
|
|
* |
|
|
|
* @param groupId |
|
|
|
* @param groupIndex |
|
|
|
* @param amountOfGraphs |
|
|
|
*/ |
|
|
|
Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) { |
|
|
|
var datapoints = this.itemsData.get({filter: function (item) {return item.group == groupId;}}); |
|
|
|
var options = this._getGroupOptions(groupId, groupIndex); |
|
|
|
|
|
|
|
// can be optimized, only has to be done once.
|
|
|
|
var group = this.groups[groupId]; |
|
|
|
|
|
|
|
if (this.options.style == 'bar') { |
|
|
|
this.drawBarGraph(datapoints, options, amountOfGraphs); |
|
|
|
this.drawBarGraph(datapoints, group, amountOfGraphs); |
|
|
|
} |
|
|
|
else { |
|
|
|
this.drawLineGraph(datapoints, options); |
|
|
|
this.drawLineGraph(datapoints, group); |
|
|
|
} |
|
|
|
}; |
|
|
|
Linegraph.prototype.drawBarGraph = function (datapoints, options, amountOfGraphs) { |
|
|
|
|
|
|
|
/** |
|
|
|
* draw a bar graph |
|
|
|
* @param datapoints |
|
|
|
* @param options |
|
|
|
* @param amountOfGraphs |
|
|
|
*/ |
|
|
|
Linegraph.prototype.drawBarGraph = function (datapoints, group, amountOfGraphs) { |
|
|
|
if (datapoints != null) { |
|
|
|
if (datapoints.length > 0) { |
|
|
|
var dataset = this._prepareData(datapoints); |
|
|
@ -3542,6 +3793,14 @@ Linegraph.prototype.drawBarGraph = function (datapoints, options, amountOfGraphs |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* draw a bar SVG element |
|
|
|
* |
|
|
|
* @param x |
|
|
|
* @param y |
|
|
|
* @param className |
|
|
|
*/ |
|
|
|
Linegraph.prototype.drawBar = function (x, y, className) { |
|
|
|
var width = 10; |
|
|
|
rect = this._getSVGElement('rect',this.svgElements, this.svg); |
|
|
@ -3553,18 +3812,25 @@ Linegraph.prototype.drawBar = function (x, y, className) { |
|
|
|
rect.setAttributeNS(null, "class", className + " point"); |
|
|
|
}; |
|
|
|
|
|
|
|
Linegraph.prototype.drawLineGraph = function (datapoints, options) { |
|
|
|
|
|
|
|
/** |
|
|
|
* draw a line graph |
|
|
|
* |
|
|
|
* @param datapoints |
|
|
|
* @param options |
|
|
|
*/ |
|
|
|
Linegraph.prototype.drawLineGraph = function (datapoints, group) { |
|
|
|
if (datapoints != null) { |
|
|
|
if (datapoints.length > 0) { |
|
|
|
var dataset = this._prepareData(datapoints, options); |
|
|
|
var dataset = this._prepareData(datapoints, group.options); |
|
|
|
var path, d; |
|
|
|
|
|
|
|
path = this._getSVGElement('path', this.svgElements, this.svg); |
|
|
|
path.setAttributeNS(null, "class", options.className); |
|
|
|
path.setAttributeNS(null, "class", group.className); |
|
|
|
|
|
|
|
|
|
|
|
// construct path from dataset
|
|
|
|
if (this.options.catmullRom.enabled == true) { |
|
|
|
if (group.options.catmullRom.enabled == true) { |
|
|
|
d = this._catmullRom(dataset); |
|
|
|
} |
|
|
|
else { |
|
|
@ -3572,52 +3838,84 @@ Linegraph.prototype.drawLineGraph = function (datapoints, options) { |
|
|
|
} |
|
|
|
|
|
|
|
// append with points for fill and finalize the path
|
|
|
|
if (options.shaded.enabled == true) { |
|
|
|
if (group.options.shaded.enabled == true) { |
|
|
|
var fillPath = this._getSVGElement('path',this.svgElements, this.svg); |
|
|
|
if (options.shaded.orientation == 'top') { |
|
|
|
if (group.options.shaded.orientation == 'top') { |
|
|
|
var dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0; |
|
|
|
} |
|
|
|
else { |
|
|
|
var dFill = "M" + dataset[0].x + "," + this.svg.offsetHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + this.svg.offsetHeight; |
|
|
|
} |
|
|
|
fillPath.setAttributeNS(null, "class", options.className + " fill"); |
|
|
|
fillPath.setAttributeNS(null, "class", group.className + " fill"); |
|
|
|
fillPath.setAttributeNS(null, "d", dFill); |
|
|
|
} |
|
|
|
// copy properties to path for drawing.
|
|
|
|
path.setAttributeNS(null, "d", "M" + d); |
|
|
|
|
|
|
|
// draw points
|
|
|
|
if (options.drawPoints.enabled == true) { |
|
|
|
this.drawPoints(dataset, options, this.svgElements, this.svg); |
|
|
|
if (group.options.drawPoints.enabled == true) { |
|
|
|
this.drawPoints(dataset, group, this.svgElements, this.svg); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
Linegraph.prototype.drawPoints = function (dataset, options, container, svg) { |
|
|
|
|
|
|
|
/** |
|
|
|
* draw the data points |
|
|
|
* |
|
|
|
* @param dataset |
|
|
|
* @param options |
|
|
|
* @param JSONcontainer |
|
|
|
* @param svg |
|
|
|
*/ |
|
|
|
Linegraph.prototype.drawPoints = function (dataset, group, JSONcontainer, svg) { |
|
|
|
for (var i = 0; i < dataset.length; i++) { |
|
|
|
this.drawPoint(dataset[i].x, dataset[i].y, options, container, svg); |
|
|
|
this.drawPoint(dataset[i].x, dataset[i].y, group, JSONcontainer, svg); |
|
|
|
} |
|
|
|
}; |
|
|
|
Linegraph.prototype.drawPoint = function(x, y, options, container, svg) { |
|
|
|
|
|
|
|
/** |
|
|
|
* draw a point object. this is a seperate function because it can also be called by the legend. |
|
|
|
* The reason the JSONcontainer and the target SVG svgContainer have to be supplied is so the legend can use these functions |
|
|
|
* as well. |
|
|
|
* |
|
|
|
* @param x |
|
|
|
* @param y |
|
|
|
* @param group |
|
|
|
* @param JSONcontainer |
|
|
|
* @param svgContainer |
|
|
|
* @returns {*} |
|
|
|
*/ |
|
|
|
Linegraph.prototype.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { |
|
|
|
var point; |
|
|
|
if (options.drawPoints.style == 'circle') { |
|
|
|
point = this._getSVGElement('circle',container,svg); |
|
|
|
point = this._getSVGElement('circle',JSONcontainer,svgContainer); |
|
|
|
point.setAttributeNS(null, "cx", x); |
|
|
|
point.setAttributeNS(null, "cy", y); |
|
|
|
point.setAttributeNS(null, "r", 0.5 * options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "class", options.className + " point"); |
|
|
|
point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "class", group.className + " point"); |
|
|
|
} |
|
|
|
else { |
|
|
|
point = this._getSVGElement('rect',container,svg); |
|
|
|
point.setAttributeNS(null, "x", x - 0.5*this.options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "y", y - 0.5*this.options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "width", this.options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "height", this.options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "class", options.className + " point"); |
|
|
|
point = this._getSVGElement('rect',JSONcontainer,svgContainer); |
|
|
|
point.setAttributeNS(null, "x", x - 0.5*group.options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "y", y - 0.5*group.options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "width", group.options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "height", group.options.drawPoints.size); |
|
|
|
point.setAttributeNS(null, "class", group.className + " point"); |
|
|
|
} |
|
|
|
return point; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer |
|
|
|
* the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this. |
|
|
|
* |
|
|
|
* @param elementType |
|
|
|
* @param JSONcontainer |
|
|
|
* @param svgContainer |
|
|
|
* @returns {*} |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._getSVGElement = function (elementType, JSONcontainer, svgContainer) { |
|
|
|
var element; |
|
|
|
// allocate SVG element, if it doesnt yet exist, create one.
|
|
|
@ -3642,26 +3940,50 @@ Linegraph.prototype._getSVGElement = function (elementType, JSONcontainer, svgCo |
|
|
|
JSONcontainer[elementType].used.push(element); |
|
|
|
return element; |
|
|
|
}; |
|
|
|
Linegraph.prototype._cleanupSVGElements = function(container) { |
|
|
|
|
|
|
|
/** |
|
|
|
* this cleans up all the unused SVG elements. By asking for the parentNode, we only need to supply the JSON container from |
|
|
|
* which to remove the redundant elements. |
|
|
|
* |
|
|
|
* @param JSONcontainer |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._cleanupSVGElements = function(JSONcontainer) { |
|
|
|
// cleanup the redundant svgElements;
|
|
|
|
for (var elementType in container) { |
|
|
|
if (container.hasOwnProperty(elementType)) { |
|
|
|
for (var i = 0; i < container[elementType].redundant.length; i++) { |
|
|
|
container[elementType].redundant[i].parentNode.removeChild(container[elementType].redundant[i]); |
|
|
|
for (var elementType in JSONcontainer) { |
|
|
|
if (JSONcontainer.hasOwnProperty(elementType)) { |
|
|
|
for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) { |
|
|
|
JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]); |
|
|
|
} |
|
|
|
container[elementType].redundant = []; |
|
|
|
JSONcontainer[elementType].redundant = []; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
Linegraph.prototype._prepareSVGElements = function(container) { |
|
|
|
|
|
|
|
/** |
|
|
|
* this prepares the JSON container for allocating SVG elements |
|
|
|
* @param JSONcontainer |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._prepareSVGElements = function(JSONcontainer) { |
|
|
|
// cleanup the redundant svgElements;
|
|
|
|
for (var elementType in container) { |
|
|
|
if (container.hasOwnProperty(elementType)) { |
|
|
|
container[elementType].redundant = container[elementType].used; |
|
|
|
container[elementType].used = []; |
|
|
|
for (var elementType in JSONcontainer) { |
|
|
|
if (JSONcontainer.hasOwnProperty(elementType)) { |
|
|
|
JSONcontainer[elementType].redundant = JSONcontainer[elementType].used; |
|
|
|
JSONcontainer[elementType].used = []; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* This uses the DataAxis object to generate the correct Y coordinate on the SVG window. It uses the |
|
|
|
* util function toScreen to get the x coordinate from the timestamp. |
|
|
|
* |
|
|
|
* @param dataset |
|
|
|
* @param options |
|
|
|
* @returns {Array} |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._prepareData = function (dataset, options) { |
|
|
|
var extractedData = []; |
|
|
|
var xValue, yValue; |
|
|
@ -3680,26 +4002,15 @@ Linegraph.prototype._prepareData = function (dataset, options) { |
|
|
|
// extractedData.sort(function (a,b) {return a.x - b.x;});
|
|
|
|
return extractedData; |
|
|
|
}; |
|
|
|
Linegraph.prototype._getGroupOptions = function(groupId, groupIndex) { |
|
|
|
var group = this.groupsData.get(groupId); |
|
|
|
var options = util.copyObject(this.options, {}); |
|
|
|
|
|
|
|
options.className = 'graphGroup' + groupIndex%10; |
|
|
|
if (group != null) { |
|
|
|
if (group.className) { |
|
|
|
options.className = group.className; |
|
|
|
} |
|
|
|
if (group.options) { |
|
|
|
var fields = ['yAxisOrientation']; |
|
|
|
util.selectiveExtend(fields, options, group.options); |
|
|
|
|
|
|
|
this._mergeOptions(options, group.options,'catmullRom'); |
|
|
|
this._mergeOptions(options, group.options,'drawPoints'); |
|
|
|
this._mergeOptions(options, group.options,'shaded'); |
|
|
|
} |
|
|
|
} |
|
|
|
return options; |
|
|
|
} |
|
|
|
/** |
|
|
|
* This uses an uniform parametrization of the CatmullRom algorithm: |
|
|
|
* "On the Parameterization of Catmull-Rom Curves" by Cem Yuksel et al. |
|
|
|
* @param data |
|
|
|
* @returns {string} |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._catmullRomUniform = function(data) { |
|
|
|
// catmull rom
|
|
|
|
var p0, p1, p2, p3, bp1, bp2; |
|
|
@ -3736,6 +4047,17 @@ Linegraph.prototype._catmullRomUniform = function(data) { |
|
|
|
|
|
|
|
return d; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* This uses either the chordal or centripetal parameterization of the catmull-rom algorithm. |
|
|
|
* By default, the centripetal parameterization is used because this gives the nicest results. |
|
|
|
* These parameterizations are relatively heavy because the distance between 4 points have to be calculated. |
|
|
|
* |
|
|
|
* One optimization can be used to reuse distances since this is a sliding window approach. |
|
|
|
* @param data |
|
|
|
* @returns {string} |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._catmullRom = function(data) { |
|
|
|
var alpha = this.options.catmullRom.alpha; |
|
|
|
if (alpha == 0 || alpha === undefined) { |
|
|
@ -3806,6 +4128,13 @@ Linegraph.prototype._catmullRom = function(data) { |
|
|
|
return d; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* this generates the SVG path for a linear drawing between datapoints. |
|
|
|
* @param data |
|
|
|
* @returns {string} |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Linegraph.prototype._linear = function(data) { |
|
|
|
// linear
|
|
|
|
var d = ""; |
|
|
@ -3820,67 +4149,6 @@ Linegraph.prototype._linear = function(data) { |
|
|
|
return d; |
|
|
|
}; |
|
|
|
|
|
|
|
Linegraph.prototype.drawLegend = function(classes) { |
|
|
|
this._prepareSVGElements(this.svgLegendElements); |
|
|
|
var x = 0; |
|
|
|
var y = 0; |
|
|
|
var lineLength = 30; |
|
|
|
var fillHeight = 10; |
|
|
|
var spacing = 25; |
|
|
|
var path, fillPath, outline; |
|
|
|
var legendWidth = 298; |
|
|
|
var padding = 5; |
|
|
|
|
|
|
|
var border = this._getSVGElement("rect", this.svgLegendElements, this.svgLegend); |
|
|
|
border.setAttributeNS(null, "x", x); |
|
|
|
border.setAttributeNS(null, "y", y); |
|
|
|
border.setAttributeNS(null, "width", legendWidth); |
|
|
|
border.setAttributeNS(null, "height", y + padding + classes.length * spacing); |
|
|
|
border.setAttributeNS(null, "class", "legendBackground"); |
|
|
|
x += 5; |
|
|
|
y += fillHeight + padding; |
|
|
|
|
|
|
|
if (classes.length > 0) { |
|
|
|
for (var i = 0; i < classes.length; i++) { |
|
|
|
outline = this._getSVGElement("rect", this.svgLegendElements, this.svgLegend); |
|
|
|
outline.setAttributeNS(null, "x", x); |
|
|
|
outline.setAttributeNS(null, "y", y - fillHeight); |
|
|
|
outline.setAttributeNS(null, "width", lineLength); |
|
|
|
outline.setAttributeNS(null, "height", 2*fillHeight); |
|
|
|
outline.setAttributeNS(null, "class", "outline"); |
|
|
|
|
|
|
|
path = this._getSVGElement("path", this.svgLegendElements, this.svgLegend); |
|
|
|
path.setAttributeNS(null, "class", classes[i]); |
|
|
|
path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + lineLength) + ","+y+""); |
|
|
|
if (this.options.shaded.enabled == true) { |
|
|
|
fillPath = this._getSVGElement("path", this.svgLegendElements, this.svgLegend); |
|
|
|
if (this.options.shaded.orientation == 'top') { |
|
|
|
fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) + |
|
|
|
"L"+x+","+y+" L"+ (x + lineLength) + ","+y+" L"+ (x + lineLength) + "," + (y - fillHeight)); |
|
|
|
} |
|
|
|
else { |
|
|
|
fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " + |
|
|
|
"L"+x+"," + (y + fillHeight) + " " + |
|
|
|
"L"+ (x + lineLength) + "," + (y + fillHeight) + |
|
|
|
"L"+ (x + lineLength) + ","+y); |
|
|
|
} |
|
|
|
fillPath.setAttributeNS(null, "class", classes[i] + " fill"); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.options.drawPoints.enabled == true) { |
|
|
|
this.drawPoint(x + 0.5 * lineLength,y,classes[i], this.svgLegendElements, this.svgLegend); |
|
|
|
} |
|
|
|
y += spacing; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
//TODO: bars
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this._cleanupSVGElements(this.svgLegendElements); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|