| @ -0,0 +1,346 @@ | |||
| /** | |||
| * Created by Alex on 5/6/14. | |||
| */ | |||
| var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items | |||
| /** | |||
| * An ItemSet holds a set of items and ranges which can be displayed in a | |||
| * range. The width is determined by the parent of the ItemSet, and the height | |||
| * is determined by the size of the items. | |||
| * @param {Panel} backgroundPanel Panel which can be used to display the | |||
| * vertical lines of box items. | |||
| * @param {Panel} axisPanel Panel on the axis where the dots of box-items | |||
| * can be displayed. | |||
| * @param {Panel} sidePanel Left side panel holding labels | |||
| * @param {Object} [options] See ItemSet.setOptions for the available options. | |||
| * @constructor ItemSet | |||
| * @extends Panel | |||
| */ | |||
| function Linegraph(backgroundPanel, axisPanel, sidePanel, options, timeline, sidePanelParent) { | |||
| this.id = util.randomUUID(); | |||
| this.timeline = timeline; | |||
| // one options object is shared by this itemset and all its items | |||
| this.options = options || {}; | |||
| this.backgroundPanel = backgroundPanel; | |||
| this.axisPanel = axisPanel; | |||
| this.sidePanel = sidePanel; | |||
| this.sidePanelParent = sidePanelParent; | |||
| this.itemOptions = Object.create(this.options); | |||
| this.dom = {}; | |||
| this.hammer = null; | |||
| this.itemsData = null; // DataSet | |||
| this.groupsData = null; // DataSet | |||
| this.range = null; // Range or Object {start: number, end: number} | |||
| // listeners for the DataSet of the items | |||
| // this.itemListeners = { | |||
| // 'add': function (event, params, senderId) { | |||
| // if (senderId != me.id) me._onAdd(params.items); | |||
| // }, | |||
| // 'update': function (event, params, senderId) { | |||
| // if (senderId != me.id) me._onUpdate(params.items); | |||
| // }, | |||
| // 'remove': function (event, params, senderId) { | |||
| // if (senderId != me.id) me._onRemove(params.items); | |||
| // } | |||
| // }; | |||
| // | |||
| // // listeners for the DataSet of the groups | |||
| // this.groupListeners = { | |||
| // 'add': function (event, params, senderId) { | |||
| // if (senderId != me.id) me._onAddGroups(params.items); | |||
| // }, | |||
| // 'update': function (event, params, senderId) { | |||
| // if (senderId != me.id) me._onUpdateGroups(params.items); | |||
| // }, | |||
| // 'remove': function (event, params, senderId) { | |||
| // if (senderId != me.id) me._onRemoveGroups(params.items); | |||
| // } | |||
| // }; | |||
| this.items = {}; // object with an Item for every data item | |||
| this.groups = {}; // Group object for every group | |||
| this.groupIds = []; | |||
| this.selection = []; // list with the ids of all selected nodes | |||
| this.stackDirty = true; // if true, all items will be restacked on next repaint | |||
| this.touchParams = {}; // stores properties while dragging | |||
| // create the HTML DOM | |||
| this.lastStart = 0; | |||
| this._create(); | |||
| var me = this; | |||
| this.timeline.on("rangechange", function() { | |||
| if (me.lastStart != 0) { | |||
| var offset = me.range.start - me.lastStart; | |||
| var range = me.range.end - me.range.start; | |||
| if (me.width != 0) { | |||
| var rangePerPixelInv = me.width/range; | |||
| var xOffset = offset * rangePerPixelInv; | |||
| me.svg.style.left = util.option.asSize(-me.width - xOffset); | |||
| } | |||
| } | |||
| }) | |||
| this.timeline.on("rangechanged", function() { | |||
| me.lastStart = me.range.start; | |||
| me.svg.style.left = util.option.asSize(-me.width); | |||
| me.setData.apply(me); | |||
| }); | |||
| } | |||
| Linegraph.prototype = new Panel(); | |||
| /** | |||
| * Create the HTML DOM for the ItemSet | |||
| */ | |||
| Linegraph.prototype._create = function _create(){ | |||
| var frame = document.createElement('div'); | |||
| frame['timeline-linegraph'] = this; | |||
| this.frame = frame; | |||
| this.frame.className = 'itemset'; | |||
| // create background panel | |||
| var background = document.createElement('div'); | |||
| background.className = 'background'; | |||
| this.backgroundPanel.frame.appendChild(background); | |||
| this.dom.background = background; | |||
| // create foreground panel | |||
| var foreground = document.createElement('div'); | |||
| foreground.className = 'foreground'; | |||
| frame.appendChild(foreground); | |||
| this.dom.foreground = foreground; | |||
| // // create axis panel | |||
| // var axis = document.createElement('div'); | |||
| // axis.className = 'axis'; | |||
| // this.dom.axis = axis; | |||
| // this.axisPanel.frame.appendChild(axis); | |||
| // | |||
| // // create labelset | |||
| // var labelSet = document.createElement('div'); | |||
| // labelSet.className = 'labelset'; | |||
| // this.dom.labelSet = labelSet; | |||
| // this.sidePanel.frame.appendChild(labelSet); | |||
| console.log(this.frame,this.frame.offsetWidth); | |||
| this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg"); | |||
| this.svg.style.position = "relative" | |||
| this.svg.style.height = "300px"; | |||
| this.path = document.createElementNS('http://www.w3.org/2000/svg',"path"); | |||
| this.path.setAttributeNS(null, "fill","none"); | |||
| this.path.setAttributeNS(null, "stroke","blue"); | |||
| this.path.setAttributeNS(null, "stroke-width","1"); | |||
| this.path2 = document.createElementNS('http://www.w3.org/2000/svg',"path"); | |||
| this.path2.setAttributeNS(null, "fill","none"); | |||
| this.path2.setAttributeNS(null, "stroke","red"); | |||
| this.path2.setAttributeNS(null, "stroke-width","2"); | |||
| this.dom.foreground.appendChild(this.svg); | |||
| this.svg.appendChild(this.path2); | |||
| this.svg.appendChild(this.path); | |||
| // create axis panel | |||
| var axis = document.createElement('div'); | |||
| axis.style.backgroundColor = 'red'; | |||
| this.dom.axis = axis; | |||
| this.axisPanel.frame.appendChild(axis); | |||
| // create labelset | |||
| var yAxis = document.createElement('div'); | |||
| yAxis.style.backgroundColor = 'blue'; | |||
| yAxis.style.width = '100px'; | |||
| yAxis.style.height = this.svg.style.height; | |||
| this.dom.yAxis = yAxis; | |||
| this.sidePanel.frame.appendChild(yAxis); | |||
| this.sidePanel.showPanel.apply(this.sidePanel); | |||
| this.sidePanelParent.showPanel(); | |||
| this.sidePanelParent.frame.style.width = "50px"; | |||
| console.log(this.sidePanelParent); | |||
| }; | |||
| Linegraph.prototype.setData = function setData() { | |||
| var data = []; | |||
| this.startTime = this.range.start; | |||
| var min = Date.now() - 3600000 * 24 * 30; | |||
| var max = Date.now() + 3600000 * 24 * 10; | |||
| var count = 60; | |||
| var step = (max-min) / count; | |||
| var range = this.range.end - this.range.start; | |||
| if (this.width != 0) { | |||
| var rangePerPixel = range/this.width; | |||
| var rangePerPixelInv = this.width/range; | |||
| var xOffset = -this.range.start + this.width*rangePerPixel; | |||
| for (var i = 0; i < count; i++) { | |||
| data.push({x:(min + i*step + xOffset) * rangePerPixelInv, y: 250*(i%2) + 25}) | |||
| } | |||
| // catmull rom | |||
| var p0, p1, p2, p3, bp1, bp2, bp3; | |||
| var d2 = "M" + data[0].x + "," + data[0].y + " "; | |||
| for (var i = 0; i < data.length - 2; i++) { | |||
| if (i == 0) { | |||
| p0 = data[0] | |||
| } | |||
| else { | |||
| p0 = data[i-1]; | |||
| } | |||
| p1 = data[i]; | |||
| p2 = data[i+1]; | |||
| p3 = data[i+2]; | |||
| // Catmull-Rom to Cubic Bezier conversion matrix | |||
| // 0 1 0 0 | |||
| // -1/6 1 1/6 0 | |||
| // 0 1/6 1 -1/6 | |||
| // 0 0 1 0 | |||
| // bp0 = { x: p1.x, y: p1.y }; | |||
| bp1 = { x: ((-p0.x + 6*p1.x + p2.x) / 6), y: ((-p0.y + 6*p1.y + p2.y) / 6)}; | |||
| bp2 = { x: ((p1.x + 6*p2.x - p3.x) / 6), y: ((p1.y + 6*p2.y - p3.y) / 6)}; | |||
| bp3 = { x: p2.x, y: p2.y }; | |||
| d2 += "C" + bp1.x + "," + bp1.y + " " + bp2.x + "," + bp2.y + " " + bp3.x + "," + bp3.y + " "; | |||
| } | |||
| // linear | |||
| var d = ""; | |||
| for (var i = 0; i < data.length - 1; i++) { | |||
| if (i == 0) { | |||
| d += "M" + data[i].x + "," + data[i].y; | |||
| } | |||
| else { | |||
| d += " " + data[i].x + "," + data[i].y; | |||
| } | |||
| } | |||
| this.path.setAttributeNS(null, "d",d); | |||
| this.path2.setAttributeNS(null, "d",d2); | |||
| } | |||
| } | |||
| /** | |||
| * Set options for the Linegraph. Existing options will be extended/overwritten. | |||
| * @param {Object} [options] The following options are available: | |||
| * {String | function} [className] | |||
| * class name for the itemset | |||
| * {String} [type] | |||
| * Default type for the items. Choose from 'box' | |||
| * (default), 'point', or 'range'. The default | |||
| * Style can be overwritten by individual items. | |||
| * {String} align | |||
| * Alignment for the items, only applicable for | |||
| * ItemBox. Choose 'center' (default), 'left', or | |||
| * 'right'. | |||
| * {String} orientation | |||
| * Orientation of the item set. Choose 'top' or | |||
| * 'bottom' (default). | |||
| * {Number} margin.axis | |||
| * Margin between the axis and the items in pixels. | |||
| * Default is 20. | |||
| * {Number} margin.item | |||
| * Margin between items in pixels. Default is 10. | |||
| * {Number} padding | |||
| * Padding of the contents of an item in pixels. | |||
| * Must correspond with the items css. Default is 5. | |||
| * {Function} snap | |||
| * Function to let items snap to nice dates when | |||
| * dragging items. | |||
| */ | |||
| Linegraph.prototype.setOptions = function setOptions(options) { | |||
| Component.prototype.setOptions.call(this, options); | |||
| }; | |||
| /** | |||
| * Set range (start and end). | |||
| * @param {Range | Object} range A Range or an object containing start and end. | |||
| */ | |||
| Linegraph.prototype.setRange = function setRange(range) { | |||
| if (!(range instanceof Range) && (!range || !range.start || !range.end)) { | |||
| throw new TypeError('Range must be an instance of Range, ' + | |||
| 'or an object containing start and end.'); | |||
| } | |||
| this.range = range; | |||
| }; | |||
| Linegraph.prototype.repaint = function repaint() { | |||
| var margin = this.options.margin, | |||
| range = this.range, | |||
| asSize = util.option.asSize, | |||
| asString = util.option.asString, | |||
| options = this.options, | |||
| orientation = this.getOption('orientation'), | |||
| resized = false, | |||
| frame = this.frame; | |||
| // TODO: document this feature to specify one margin for both item and axis distance | |||
| if (typeof margin === 'number') { | |||
| margin = { | |||
| item: margin, | |||
| axis: margin | |||
| }; | |||
| } | |||
| // update className | |||
| this.frame.className = 'itemset' + (options.className ? (' ' + asString(options.className)) : ''); | |||
| // check whether zoomed (in that case we need to re-stack everything) | |||
| // TODO: would be nicer to get this as a trigger from Range | |||
| var visibleInterval = this.range.end - this.range.start; | |||
| var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.width != this.lastWidth); | |||
| if (zoomed) this.stackDirty = true; | |||
| this.lastVisibleInterval = visibleInterval; | |||
| this.lastWidth = this.width; | |||
| // reposition frame | |||
| this.frame.style.left = asSize(options.left, ''); | |||
| this.frame.style.right = asSize(options.right, ''); | |||
| this.frame.style.top = asSize((orientation == 'top') ? '0' : ''); | |||
| this.frame.style.bottom = asSize((orientation == 'top') ? '' : '0'); | |||
| this.frame.style.width = asSize(options.width, '100%'); | |||
| // frame.style.height = asSize(height); | |||
| //frame.style.height = asSize('height' in options ? options.height : height); // TODO: reckon with height | |||
| // calculate actual size and position | |||
| this.top = this.frame.offsetTop; | |||
| this.left = this.frame.offsetLeft; | |||
| this.width = this.frame.offsetWidth; | |||
| // this.height = height; | |||
| // check if this component is resized | |||
| resized = this._isResized() || resized; | |||
| if (resized) { | |||
| this.svg.style.width = asSize(3*this.width); | |||
| this.svg.style.left = asSize(-this.width); | |||
| } | |||
| if (zoomed) { | |||
| this.setData(); | |||
| } | |||
| } | |||