From 6ad2f0e8ef74ffcb65a31432bc1a1dd10d41bf15 Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Mon, 23 Jun 2014 14:25:59 +0200 Subject: [PATCH] structure, bugs, barchart, icons --- Jakefile.js | 2 +- dist/vis.css | 1 + dist/vis.js | 485 ++++++++++------------ src/{svgUtil.js => DOMutil.js} | 69 ++- src/timeline/DataStep.js | 22 - src/timeline/component/DataAxis.js | 180 +++----- src/timeline/component/GraphGroup.js | 33 +- src/timeline/component/Legend.js | 12 +- src/timeline/component/Linegraph.js | 169 ++++---- src/timeline/component/css/pathStyles.css | 1 + 10 files changed, 435 insertions(+), 539 deletions(-) rename src/{svgUtil.js => DOMutil.js} (60%) diff --git a/Jakefile.js b/Jakefile.js index 360c59e9..384b6ef9 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -64,7 +64,7 @@ task('build', {async: true}, function () { './src/shim.js', './src/util.js', - './src/SVGutil.js', + './src/DOMutil.js', './src/DataSet.js', './src/DataView.js', diff --git a/dist/vis.css b/dist/vis.css index efe123cf..dbf1cd32 100644 --- a/dist/vis.css +++ b/dist/vis.css @@ -450,6 +450,7 @@ .vis.timeline .bar { fill-opacity:0.7; + stroke-width:1px; } .vis.timeline .point { diff --git a/dist/vis.js b/dist/vis.js index e00e1929..072d6c51 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -1370,13 +1370,13 @@ util._mergeOptions = function (mergeTarget, options, option) { * Created by Alex on 6/20/14. */ -var SVGutil = {} +var DOMutil = {} /** * this prepares the JSON container for allocating SVG elements * @param JSONcontainer * @private */ -SVGutil._prepareSVGElements = function(JSONcontainer) { +DOMutil.prepareElements = function(JSONcontainer) { // cleanup the redundant svgElements; for (var elementType in JSONcontainer) { if (JSONcontainer.hasOwnProperty(elementType)) { @@ -1393,7 +1393,7 @@ SVGutil._prepareSVGElements = function(JSONcontainer) { * @param JSONcontainer * @private */ -SVGutil._cleanupSVGElements = function(JSONcontainer) { +DOMutil.cleanupElements = function(JSONcontainer) { // cleanup the redundant svgElements; for (var elementType in JSONcontainer) { if (JSONcontainer.hasOwnProperty(elementType)) { @@ -1415,7 +1415,7 @@ SVGutil._cleanupSVGElements = function(JSONcontainer) { * @returns {*} * @private */ -SVGutil._getSVGElement = function (elementType, JSONcontainer, svgContainer) { +DOMutil.getSVGElement = function (elementType, JSONcontainer, svgContainer) { var element; // allocate SVG element, if it doesnt yet exist, create one. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before @@ -1441,6 +1441,43 @@ SVGutil._getSVGElement = function (elementType, JSONcontainer, svgContainer) { }; +/** + * 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 + */ +DOMutil.getDOMElement = function (elementType, JSONcontainer, DOMContainer) { + var element; + // allocate SVG element, if it doesnt yet exist, create one. + if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before + // check if there is an redundant element + if (JSONcontainer[elementType].redundant.length > 0) { + element = JSONcontainer[elementType].redundant[0]; + JSONcontainer[elementType].redundant.shift() + } + else { + // create a new element and add it to the SVG + element = document.createElement(elementType); + DOMContainer.appendChild(element); + } + } + else { + // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it. + element = document.createElement(elementType); + JSONcontainer[elementType] = {used: [], redundant: []}; + DOMContainer.appendChild(element); + } + JSONcontainer[elementType].used.push(element); + return element; +}; + + + /** * draw a point object. this is a seperate function because it can also be called by the legend. @@ -1454,17 +1491,17 @@ SVGutil._getSVGElement = function (elementType, JSONcontainer, svgContainer) { * @param svgContainer * @returns {*} */ -SVGutil.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { +DOMutil.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { var point; if (group.options.drawPoints.style == 'circle') { - point = SVGutil._getSVGElement('circle',JSONcontainer,svgContainer); + point = DOMutil.getSVGElement('circle',JSONcontainer,svgContainer); point.setAttributeNS(null, "cx", x); point.setAttributeNS(null, "cy", y); point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size); point.setAttributeNS(null, "class", group.className + " point"); } else { - point = SVGutil._getSVGElement('rect',JSONcontainer,svgContainer); + point = DOMutil.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); @@ -1472,7 +1509,23 @@ SVGutil.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { point.setAttributeNS(null, "class", group.className + " point"); } return point; -} +}; + +/** + * draw a bar SVG element + * + * @param x + * @param y + * @param className + */ +DOMutil.drawBar = function (x, y, width, height, className, JSONcontainer, svgContainer) { + rect = DOMutil.getSVGElement('rect',JSONcontainer, svgContainer); + rect.setAttributeNS(null, "x", Math.round(x - 0.5 * width)); + rect.setAttributeNS(null, "y", Math.round(y)); + rect.setAttributeNS(null, "width", width); + rect.setAttributeNS(null, "height", height); + rect.setAttributeNS(null, "class", className); +}; /** * DataSet * @@ -2735,20 +2788,21 @@ GraphGroup.prototype.update = function(group) { GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, iconWidth, iconHeight) { var fillHeight = iconHeight * 0.5; - var path, fillPath, outline; + var path, fillPath; + + var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer); + outline.setAttributeNS(null, "x", x); + outline.setAttributeNS(null, "y", y - fillHeight); + outline.setAttributeNS(null, "width", iconWidth); + outline.setAttributeNS(null, "height", 2*fillHeight); + outline.setAttributeNS(null, "class", "outline"); + if (this.options.style == 'line') { - outline = SVGutil._getSVGElement("rect", JSONcontainer, SVGcontainer); - outline.setAttributeNS(null, "x", x); - outline.setAttributeNS(null, "y", y - fillHeight); - outline.setAttributeNS(null, "width", iconWidth); - outline.setAttributeNS(null, "height", 2*fillHeight); - outline.setAttributeNS(null, "class", "outline"); - - path = SVGutil._getSVGElement("path", JSONcontainer, SVGcontainer); + path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer); path.setAttributeNS(null, "class", this.className); path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+""); if (this.options.shaded.enabled == true) { - fillPath = SVGutil._getSVGElement("path", JSONcontainer, SVGcontainer); + fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer); if (this.options.shaded.orientation == 'top') { fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) + "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight)); @@ -2763,12 +2817,18 @@ GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, iconWi } if (this.options.drawPoints.enabled == true) { - SVGutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer); + DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer); } } else { - console.log("bar") - //TODO: bars + var barWidth = Math.round(0.3 * iconWidth); + var bar1Height = Math.round(0.4 * iconHeight); + var bar2Height = Math.round(0.75 * iconHeight); + + var offset = Math.round((iconWidth - (2 * barWidth))/3); + + DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer); + DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer); } } @@ -2868,7 +2928,7 @@ Legend.prototype.redraw = function() { } Legend.prototype.drawLegend = function() { - this.linegraph._prepareSVGElements.call(this,this.svgElements); + this.linegraph.prepareElements.call(this,this.svgElements); var x = 0; var y = 0; var lineLength = 30; @@ -2878,7 +2938,7 @@ Legend.prototype.drawLegend = function() { var legendWidth = 298; var padding = 5; - var border = this._getSVGElement("rect", this.svgLegendElements, this.svgLegend); + var border = this.getSVGElement("rect", this.svgLegendElements, this.svgLegend); border.setAttributeNS(null, "x", x); border.setAttributeNS(null, "y", y); border.setAttributeNS(null, "width", legendWidth); @@ -2889,18 +2949,18 @@ Legend.prototype.drawLegend = function() { if (classes.length > 0) { for (var i = 0; i < classes.length; i++) { - outline = this._getSVGElement("rect", this.svgLegendElements, this.svgLegend); + 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 = 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); + 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)); @@ -2926,7 +2986,7 @@ Legend.prototype.drawLegend = function() { - this._cleanupSVGElements(this.svgLegendElements); + this.cleanupElements(this.svgLegendElements); } /** * A horizontal time axis @@ -2934,6 +2994,7 @@ Legend.prototype.drawLegend = function() { * options. * @constructor DataAxis * @extends Component + * @param body */ function DataAxis (body, options) { this.id = util.randomUUID(); @@ -2954,15 +3015,13 @@ function DataAxis (body, options) { }; this.props = {}; - this.dom = { - lines: [], - labels: [], - redundant: { - lines: [], - labels: [] - } + this.DOMelements = { // dynamic elements + lines: {}, + labels: {} }; + this.dom = {}; + this.yRange = {start:0, end:0}; this.options = util.extend({}, this.defaultOptions); @@ -3006,7 +3065,7 @@ DataAxis.prototype.deleteGroup = function(label) { }; -DataAxis.prototype.setOptions = function(options) { +DataAxis.prototype.setOptions = function (options) { if (options) { var redraw = false; if (this.options.orientation != options.orientation && options.orientation !== undefined) { @@ -3030,7 +3089,7 @@ DataAxis.prototype.setOptions = function(options) { this.show(); } } -} +}; /** @@ -3055,14 +3114,15 @@ DataAxis.prototype._create = function() { this.dom.frame.appendChild(this.svg); }; -DataAxis.prototype._redrawGroupIcons = function() { - SVGutil._prepareSVGElements(this.svgContainer); +DataAxis.prototype._redrawGroupIcons = function () { + DOMutil.prepareElements(this.svgElements); var x; var iconWidth = this.options.iconWidth; var iconHeight = 15; var iconOffset = 4; var y = iconOffset + 0.5 * iconHeight; + if (this.options.orientation == 'left') { x = iconOffset; } @@ -3077,8 +3137,8 @@ DataAxis.prototype._redrawGroupIcons = function() { } } - SVGutil._cleanupSVGElements(this.svgContainer); -} + DOMutil.cleanupElements(this.svgElements); +}; /** * Create the HTML DOM for the DataAxis @@ -3113,14 +3173,13 @@ DataAxis.prototype.hide = function() { /** * Set a range (start and end) - * @param {Range | Object} range A Range or an object containing start and end. + * @param end + * @param start + * @param end */ -DataAxis.prototype.setRange = function (range) { - if (!(range instanceof Range) && (!range || range.start === undefined || range.end === undefined)) { - throw new TypeError('Range must be an instance of Range, ' + 'or an object containing start and end.'); - } - this.yRange.start = range.start; - this.yRange.end = range.end; +DataAxis.prototype.setRange = function (start, end) { + this.yRange.start = start; + this.yRange.end = end; }; /** @@ -3147,7 +3206,7 @@ DataAxis.prototype.redraw = function () { props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2*this.options.minorLinesOffset; props.minorLineHeight = 1; - props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2*this.options.majorLinesOffset;; + props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset; props.majorLineHeight = 1; // take frame offline while updating (is almost twice as fast) @@ -3177,26 +3236,19 @@ DataAxis.prototype.redraw = function () { * @private */ DataAxis.prototype._redrawLabels = function () { + DOMutil.prepareElements(this.DOMelements); + var orientation = this.options['orientation']; // calculate range and step (step such that we have space for 7 characters per label) var start = this.yRange.start; var end = this.yRange.end; - var minimumStep = (this.props.minorCharHeight || 10); //in pixels + var minimumStep = (this.props.majorCharHeight || 10); //in pixels var step = new DataStep(start, end, minimumStep, this.dom.frame.offsetHeight); this.step = step; step.first(); - - // Move all DOM elements to a "redundant" list, where they - // can be picked for re-use, and clear the lists with lines and texts. - // At the end of the function _redrawLabels, left over elements will be cleaned up - var dom = this.dom; - dom.redundant.lines = dom.lines; - dom.redundant.labels = dom.labels; - dom.lines = []; - dom.labels = []; - + // get the distance in pixels for a step var stepPixels = this.dom.frame.offsetHeight / ((step.marginRange / step.step) + 1); this.stepPixels = stepPixels; @@ -3212,7 +3264,6 @@ DataAxis.prototype._redrawLabels = function () { amountOfSteps = this.height / stepPixels; } - var xFirstMajorLabel = undefined; this.valueAtZero = step.marginEnd; var marginStartPos = 0; @@ -3227,21 +3278,18 @@ DataAxis.prototype._redrawLabels = function () { marginStartPos = max * stepPixels; var isMajor = step.isMajor(); - if (this.options['showMinorLabels'] && isMajor == false) { + if (this.options['showMinorLabels'] && isMajor == false || this.master == false) { this._redrawLabel(y - 2, step.current, orientation, 'yAxis minor', this.props.minorCharHeight); } - if (isMajor && this.options['showMajorLabels']) { + if (isMajor && this.options['showMajorLabels'] && this.master == true) { if (y >= 0) { - if (xFirstMajorLabel == undefined) { - xFirstMajorLabel = y; - } this._redrawLabel(y - 2, step.current, orientation, 'yAxis major', this.props.majorCharHeight); } - this._redrawMajorLine(y, orientation); + this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth); } else { - this._redrawMinorLine(y, orientation); + this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth); } step.next(); @@ -3260,55 +3308,23 @@ DataAxis.prototype._redrawLabels = function () { this.conversionFactor = marginStartPos/((amountOfSteps-1) * step.step); - // Cleanup leftover DOM elements from the redundant list - util.forEach(this.dom.redundant, function (arr) { - while (arr.length) { - var elem = arr.pop(); - if (elem && elem.parentNode) { - elem.parentNode.removeChild(elem); - } - } - }); + DOMutil.cleanupElements(this.DOMelements); }; -DataAxis.prototype.convertValues = function(data) { - for (var i = 0; i < data.length; i++) { - data[i].y = this.convertValue(data[i].y); - } - return data; -} - -DataAxis.prototype.convertValue = function(value) { - var invertedValue = this.valueAtZero - value; - var convertedValue = invertedValue * this.conversionFactor; - return convertedValue; // the -2 is to compensate for the borders -} - - - /** * Create a label for the axis at position x - * @param {Number} x - * @param {String} text - * @param {String} orientation "top" or "bottom" (default) * @private + * @param y + * @param text + * @param orientation + * @param className + * @param characterHeight */ DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) { // reuse redundant label - var label = this.dom.redundant.labels.shift(); - - if (!label) { - // create label - var content = document.createTextNode(text); - label = document.createElement('div'); - label.className = className; - label.appendChild(content); - this.dom.frame.appendChild(label); - } - this.dom.labels.push(label); - - label.childNodes[0].nodeValue = text; - //label.title = title; // TODO: this is a heavy operation + var label = DOMutil.getDOMElement('div',this.DOMelements, this.dom.frame); //this.dom.redundant.labels.shift(); + label.className = className; + label.innerHTML = text; if (orientation == 'left') { label.style.left = '-' + this.options.labelOffsetX + 'px'; @@ -3327,70 +3343,40 @@ DataAxis.prototype._redrawLabel = function (y, text, orientation, className, cha if (this.maxLabelSize < text.length * largestWidth) { this.maxLabelSize = text.length * largestWidth; } - - }; /** * Create a minor line for the axis at position y - * @param {Number} y - * @param {String} orientation "top" or "bottom" (default) - * @private + * @param y + * @param orientation + * @param className + * @param offset + * @param width */ -DataAxis.prototype._redrawMinorLine = function (y, orientation) { +DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) { if (this.master == true) { // reuse redundant line - var line = this.dom.redundant.lines.shift(); - - if (!line) { - // create vertical line - line = document.createElement('div'); - line.className = 'grid horizontal minor'; - this.dom.lineContainer.appendChild(line); - } - this.dom.lines.push(line); + var line = DOMutil.getDOMElement('div',this.DOMelements, this.dom.lineContainer);//this.dom.redundant.lines.shift(); + line.className = className; + line.innerHTML = ''; - var props = this.props; if (orientation == 'left') { - line.style.left = (this.width - this.options.minorLinesOffset) + 'px'; + line.style.left = (this.width - offset) + 'px'; } else { - line.style.left = -1*(this.width - this.options.minorLinesOffset) + 'px'; + line.style.left = -1*(this.width - offset) + 'px'; } - line.style.width = props.minorLineWidth + 'px'; + line.style.width = width + 'px'; line.style.top = y + 'px'; } }; -/** - * Create a Major line for the axis at position x - * @param {Number} x - * @param {String} orientation "top" or "bottom" (default) - * @private - */ -DataAxis.prototype._redrawMajorLine = function (y, orientation) { - if (this.master == true) { - // reuse redundant line - var line = this.dom.redundant.lines.shift(); - - if (!line) { - // create vertical line - line = document.createElement('div'); - line.className = 'grid horizontal major'; - this.dom.lineContainer.appendChild(line); - } - this.dom.lines.push(line); - if (orientation == 'left') { - line.style.left = (this.width - this.options.majorLinesOffset) + 'px'; - } - else { - line.style.left = -1*(this.width - this.options.majorLinesOffset) + 'px'; - } - line.style.top = y + 'px'; - line.style.width = this.props.majorLineWidth + 'px'; - } +DataAxis.prototype.convertValue = function (value) { + var invertedValue = this.valueAtZero - value; + var convertedValue = invertedValue * this.conversionFactor; + return convertedValue; // the -2 is to compensate for the borders }; @@ -3538,7 +3524,6 @@ function Linegraph(body, options) { this.setOptions(options); this.groupsUsingDefaultStyles = [0]; - var me = this; this.body.emitter.on("rangechange",function() { if (me.lastStart != 0) { var offset = me.body.range.start - me.lastStart; @@ -3756,27 +3741,7 @@ Linegraph.prototype._onRemove = 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.groupsUsingDefaultStyles); - this.legend.addGroup(groupIds[i],this.groups[groupIds[i]]); - - if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { - this.yAxisRight.addGroup(groupIds[i], this.groups[groupIds[i]]); - } - else { - this.yAxisLeft.addGroup(groupIds[i], this.groups[groupIds[i]]); - } - } - else { - this.groups[groupIds[i]].update(group); - this.legend.updateGroup(groupIds[i],this.groups[groupIds[i]]); - if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { - this.yAxisRight.updateGroup(groupIds[i], this.groups[groupIds[i]]); - } - else { - this.yAxisLeft.updateGroup(groupIds[i], this.groups[groupIds[i]]); - } - } + this._updateGroup(group, groupIds[i]); } this._updateUngrouped(); this._updateGraph(); @@ -3793,34 +3758,40 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) { this.redraw(); }; -/** - * Create or delete the group holding all ungrouped items. This group is used when - * there are no groups specified. This anonymous group is called 'graph'. - * @protected - */ -Linegraph.prototype._updateUngrouped = function() { - var group = {id: UNGROUPED, content: "graph"}; - if (!this.groups.hasOwnProperty(UNGROUPED)) { - this.groups[UNGROUPED] = new GraphGroup(group, this.options, this.groupsUsingDefaultStyles); - this.legend.addGroup(UNGROUPED,this.groups[UNGROUPED]); - if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { - this.yAxisRight.addGroup(UNGROUPED, this.groups[UNGROUPED]); +Linegraph.prototype._updateGroup = function (group, groupId) { + + if (!this.groups.hasOwnProperty(groupId)) { + this.groups[groupId] = new GraphGroup(group, this.options, this.groupsUsingDefaultStyles); + this.legend.addGroup(groupId, this.groups[groupId]); + + if (this.groups[groupId].options.yAxisOrientation == 'right') { + this.yAxisRight.addGroup(groupId, this.groups[groupId]); } else { - this.yAxisLeft.addGroup(UNGROUPED, this.groups[UNGROUPED]); + this.yAxisLeft.addGroup(groupId, this.groups[groupId]); } } else { - this.groups[UNGROUPED].update(group); - this.legend.updateGroup(UNGROUPED,this.groups[UNGROUPED]); - if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { - this.yAxisRight.updateGroup(UNGROUPED, this.groups[UNGROUPED]); + this.groups[groupId].update(group); + this.legend.updateGroup(groupId, this.groups[groupId]); + if (this.groups[groupId].options.yAxisOrientation == 'right') { + this.yAxisRight.updateGroup(groupId, this.groups[groupId]); } else { - this.yAxisLeft.updateGroup(UNGROUPED, this.groups[UNGROUPED]); + this.yAxisLeft.updateGroup(groupId, this.groups[groupId]); } } +}; + +/** + * Create or delete the group holding all ungrouped items. This group is used when + * there are no groups specified. This anonymous group is called 'graph'. + * @protected + */ +Linegraph.prototype._updateUngrouped = function() { + var group = {id: UNGROUPED, content: "graph"}; + this._updateGroup(group, UNGROUPED); if (this.itemsData != null) { var datapoints = this.itemsData.get({ @@ -3885,7 +3856,7 @@ Linegraph.prototype.redraw = function() { */ Linegraph.prototype._updateGraph = function () { // reset the svg elements - SVGutil._prepareSVGElements(this.svgElements); + DOMutil.prepareElements(this.svgElements); if (this.width != 0 && this.itemsData != null) { // look at different lines @@ -3898,10 +3869,8 @@ Linegraph.prototype._updateGraph = function () { } } - // this.legend.redraw(); - // cleanup unused svg elements - SVGutil._cleanupSVGElements(this.svgElements); + DOMutil.cleanupElements(this.svgElements); }; /** @@ -3909,7 +3878,7 @@ Linegraph.prototype._updateGraph = function () { * @param {array} groupIds * @private */ -Linegraph.prototype._updateYAxis = function(groupIds) { +Linegraph.prototype._updateYAxis = function (groupIds) { var yAxisLeftUsed = false; var yAxisRightUsed = false; var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal; @@ -3925,28 +3894,38 @@ Linegraph.prototype._updateYAxis = function(groupIds) { orientation = 'right'; } - var view = new vis.DataSet(this.itemsData.get({filter: function (item) {return item.group == groupIds[i];}})); + var view = new vis.DataSet(this.itemsData.get({filter: function (item) { + return item.group == groupIds[i]; + }})); minVal = view.min("y").y; maxVal = view.max("y").y; if (orientation == 'left') { yAxisLeftUsed = true; - if (minLeft > minVal) {minLeft = minVal;} - if (maxLeft < maxVal) {maxLeft = maxVal;} + if (minLeft > minVal) { + minLeft = minVal; + } + if (maxLeft < maxVal) { + maxLeft = maxVal; + } } else { yAxisRightUsed = true; - if (minRight > minVal) {minRight = minVal;} - if (maxRight < maxVal) {maxRight = maxVal;} + if (minRight > minVal) { + minRight = minVal; + } + if (maxRight < maxVal) { + maxRight = maxVal; + } } delete view; } - if (yAxisLeftUsed == true) { - this.yAxisLeft.setRange({start: minLeft, end: maxLeft}); + if (yAxisLeftUsed == true) { + this.yAxisLeft.setRange(minLeft, maxLeft); } if (yAxisRightUsed == true) { - this.yAxisRight.setRange({start: minRight, end: maxRight}); + this.yAxisRight.setRange(minRight, maxRight); } } @@ -3957,11 +3936,11 @@ Linegraph.prototype._updateYAxis = function(groupIds) { } if (yAxisRightUsed == true && yAxisLeftUsed == true) { - this.yAxisLeft.drawIcons = true; + this.yAxisLeft.drawIcons = true; this.yAxisRight.drawIcons = true; } else { - this.yAxisLeft.drawIcons = false; + this.yAxisLeft.drawIcons = false; this.yAxisRight.drawIcons = false; } @@ -3978,17 +3957,17 @@ Linegraph.prototype._updateYAxis = function(groupIds) { else { this.yAxisRight.redraw(); } -} +}; /** * 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 + * @param axis */ -Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) { +Linegraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) { var changed = false; if (axisUsed == false) { if (axis.dom.frame.parentNode) { @@ -4003,7 +3982,7 @@ Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) { } } return changed; -} +}; /** @@ -4023,24 +4002,30 @@ Linegraph.prototype._drawGraph = function (groupId, groupIndex, amountOfGraphs) this._drawLineGraph(datapoints, group); } else { - this._drawBarGraph(datapoints, group, amountOfGraphs); + this._drawBarGraph(datapoints, group); } }; /** * draw a bar graph * @param datapoints - * @param options - * @param amountOfGraphs + * @param group */ -Linegraph.prototype._drawBarGraph = function (datapoints, group, amountOfGraphs) { +Linegraph.prototype._drawBarGraph = function (datapoints, group) { if (datapoints != null) { if (datapoints.length > 0) { var dataset = this._prepareData(datapoints, group.options); - var bar; - console.log(group.options); + var width, coreDistance; + var minWidth = 0.1 * group.options.barChart.width; + for (var i = 0; i < dataset.length; i++) { - this._drawBar(dataset[i].x, dataset[i].y, group.options.barChart.width, this.zeroPosition - dataset[i].y, group.className + ' bar'); + width = group.options.barChart.width; + // dynammically downscale the width so there is no overlap up to 1/10th the original width + if (i+1 < dataset.length) {coreDistance = Math.abs(dataset[i+1].x - dataset[i].x);} + if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(dataset[i-1].x - dataset[i].x));} + if (coreDistance < width) {width = coreDistance < minWidth ? minWidth : coreDistance;} + + DOMutil.drawBar(dataset[i].x, dataset[i].y, width, this.zeroPosition - dataset[i].y, group.className + ' bar', this.svgElements, this.svg); } // draw points @@ -4051,29 +4036,12 @@ Linegraph.prototype._drawBarGraph = function (datapoints, group, amountOfGraphs) } }; -/** - * draw a bar SVG element - * - * @param x - * @param y - * @param className - */ -Linegraph.prototype._drawBar = function (x, y, width, height, className) { - rect = SVGutil._getSVGElement('rect',this.svgElements, this.svg); - - rect.setAttributeNS(null, "x", x - 0.5 * width); - rect.setAttributeNS(null, "y", y); - rect.setAttributeNS(null, "width", width); - rect.setAttributeNS(null, "height", height); - rect.setAttributeNS(null, "class", className); -}; - /** * draw a line graph - * + * * @param datapoints - * @param options + * @param group */ Linegraph.prototype._drawLineGraph = function (datapoints, group) { if (datapoints != null) { @@ -4081,7 +4049,7 @@ Linegraph.prototype._drawLineGraph = function (datapoints, group) { var dataset = this._prepareData(datapoints, group.options); var path, d; - path = SVGutil._getSVGElement('path', this.svgElements, this.svg); + path = DOMutil.getSVGElement('path', this.svgElements, this.svg); path.setAttributeNS(null, "class", group.className); @@ -4095,12 +4063,13 @@ Linegraph.prototype._drawLineGraph = function (datapoints, group) { // append with points for fill and finalize the path if (group.options.shaded.enabled == true) { - var fillPath = SVGutil._getSVGElement('path',this.svgElements, this.svg); + var fillPath = DOMutil.getSVGElement('path',this.svgElements, this.svg); + var dFill; if (group.options.shaded.orientation == 'top') { - var dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0; + 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; + dFill = "M" + dataset[0].x + "," + this.svg.offsetHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + this.svg.offsetHeight; } fillPath.setAttributeNS(null, "class", group.className + " fill"); fillPath.setAttributeNS(null, "d", dFill); @@ -4118,15 +4087,15 @@ Linegraph.prototype._drawLineGraph = function (datapoints, group) { /** * draw the data points - * + * * @param dataset - * @param options * @param JSONcontainer * @param svg + * @param group */ Linegraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg) { for (var i = 0; i < dataset.length; i++) { - SVGutil.drawPoint(dataset[i].x, dataset[i].y, group, JSONcontainer, svg); + DOMutil.drawPoint(dataset[i].x, dataset[i].y, group, JSONcontainer, svg); } }; @@ -4152,7 +4121,7 @@ Linegraph.prototype._prepareData = function (datapoints, options) { axis = this.yAxisRight; } for (var i = 0; i < datapoints.length; i++) { - xValue = toScreen(datapoints[i].x) + this.width; + xValue = toScreen(datapoints[i].x) + this.width - 1; yValue = axis.convertValue(datapoints[i].y); extractedData.push({x: xValue, y: yValue}); } @@ -4508,28 +4477,6 @@ DataStep.prototype.isMajor = function() { return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0); }; - -/** - * Returns formatted text for the minor axislabel, depending on the current - * date and the scale. For example when scale is MINUTE, the current time is - * formatted as "hh:mm". - * @param {Date} [date] custom date. if not provided, current date is taken - */ -DataStep.prototype.getLabelMinor = function() { - return this.current; -}; - - -/** - * Returns formatted text for the major axis label, depending on the current - * date and the scale. For example when scale is MINUTE, the major scale is - * hours, and the hour will be formatted as "hh". - * @param {Date} [date] custom date. if not provided, current date is taken - */ -DataStep.prototype.getLabelMajor = function() { - return this.current; -}; - /** * Utility functions for ordering and stacking of items */ diff --git a/src/svgUtil.js b/src/DOMutil.js similarity index 60% rename from src/svgUtil.js rename to src/DOMutil.js index 16d77f71..9775ce31 100644 --- a/src/svgUtil.js +++ b/src/DOMutil.js @@ -2,13 +2,13 @@ * Created by Alex on 6/20/14. */ -var SVGutil = {} +var DOMutil = {} /** * this prepares the JSON container for allocating SVG elements * @param JSONcontainer * @private */ -SVGutil._prepareSVGElements = function(JSONcontainer) { +DOMutil.prepareElements = function(JSONcontainer) { // cleanup the redundant svgElements; for (var elementType in JSONcontainer) { if (JSONcontainer.hasOwnProperty(elementType)) { @@ -25,7 +25,7 @@ SVGutil._prepareSVGElements = function(JSONcontainer) { * @param JSONcontainer * @private */ -SVGutil._cleanupSVGElements = function(JSONcontainer) { +DOMutil.cleanupElements = function(JSONcontainer) { // cleanup the redundant svgElements; for (var elementType in JSONcontainer) { if (JSONcontainer.hasOwnProperty(elementType)) { @@ -47,7 +47,7 @@ SVGutil._cleanupSVGElements = function(JSONcontainer) { * @returns {*} * @private */ -SVGutil._getSVGElement = function (elementType, JSONcontainer, svgContainer) { +DOMutil.getSVGElement = function (elementType, JSONcontainer, svgContainer) { var element; // allocate SVG element, if it doesnt yet exist, create one. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before @@ -73,6 +73,43 @@ SVGutil._getSVGElement = function (elementType, JSONcontainer, svgContainer) { }; +/** + * 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 + */ +DOMutil.getDOMElement = function (elementType, JSONcontainer, DOMContainer) { + var element; + // allocate SVG element, if it doesnt yet exist, create one. + if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before + // check if there is an redundant element + if (JSONcontainer[elementType].redundant.length > 0) { + element = JSONcontainer[elementType].redundant[0]; + JSONcontainer[elementType].redundant.shift() + } + else { + // create a new element and add it to the SVG + element = document.createElement(elementType); + DOMContainer.appendChild(element); + } + } + else { + // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it. + element = document.createElement(elementType); + JSONcontainer[elementType] = {used: [], redundant: []}; + DOMContainer.appendChild(element); + } + JSONcontainer[elementType].used.push(element); + return element; +}; + + + /** * draw a point object. this is a seperate function because it can also be called by the legend. @@ -86,17 +123,17 @@ SVGutil._getSVGElement = function (elementType, JSONcontainer, svgContainer) { * @param svgContainer * @returns {*} */ -SVGutil.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { +DOMutil.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { var point; if (group.options.drawPoints.style == 'circle') { - point = SVGutil._getSVGElement('circle',JSONcontainer,svgContainer); + point = DOMutil.getSVGElement('circle',JSONcontainer,svgContainer); point.setAttributeNS(null, "cx", x); point.setAttributeNS(null, "cy", y); point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size); point.setAttributeNS(null, "class", group.className + " point"); } else { - point = SVGutil._getSVGElement('rect',JSONcontainer,svgContainer); + point = DOMutil.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); @@ -104,4 +141,20 @@ SVGutil.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { point.setAttributeNS(null, "class", group.className + " point"); } return point; -} \ No newline at end of file +}; + +/** + * draw a bar SVG element + * + * @param x + * @param y + * @param className + */ +DOMutil.drawBar = function (x, y, width, height, className, JSONcontainer, svgContainer) { + rect = DOMutil.getSVGElement('rect',JSONcontainer, svgContainer); + rect.setAttributeNS(null, "x", Math.round(x - 0.5 * width)); + rect.setAttributeNS(null, "y", Math.round(y)); + rect.setAttributeNS(null, "width", width); + rect.setAttributeNS(null, "height", height); + rect.setAttributeNS(null, "class", className); +}; \ No newline at end of file diff --git a/src/timeline/DataStep.js b/src/timeline/DataStep.js index 0e040412..a28a9084 100644 --- a/src/timeline/DataStep.js +++ b/src/timeline/DataStep.js @@ -192,25 +192,3 @@ DataStep.prototype.snap = function(date) { DataStep.prototype.isMajor = function() { return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0); }; - - -/** - * Returns formatted text for the minor axislabel, depending on the current - * date and the scale. For example when scale is MINUTE, the current time is - * formatted as "hh:mm". - * @param {Date} [date] custom date. if not provided, current date is taken - */ -DataStep.prototype.getLabelMinor = function() { - return this.current; -}; - - -/** - * Returns formatted text for the major axis label, depending on the current - * date and the scale. For example when scale is MINUTE, the major scale is - * hours, and the hour will be formatted as "hh". - * @param {Date} [date] custom date. if not provided, current date is taken - */ -DataStep.prototype.getLabelMajor = function() { - return this.current; -}; diff --git a/src/timeline/component/DataAxis.js b/src/timeline/component/DataAxis.js index 6c8babc0..28de1782 100644 --- a/src/timeline/component/DataAxis.js +++ b/src/timeline/component/DataAxis.js @@ -4,6 +4,7 @@ * options. * @constructor DataAxis * @extends Component + * @param body */ function DataAxis (body, options) { this.id = util.randomUUID(); @@ -24,15 +25,13 @@ function DataAxis (body, options) { }; this.props = {}; - this.dom = { - lines: [], - labels: [], - redundant: { - lines: [], - labels: [] - } + this.DOMelements = { // dynamic elements + lines: {}, + labels: {} }; + this.dom = {}; + this.yRange = {start:0, end:0}; this.options = util.extend({}, this.defaultOptions); @@ -76,7 +75,7 @@ DataAxis.prototype.deleteGroup = function(label) { }; -DataAxis.prototype.setOptions = function(options) { +DataAxis.prototype.setOptions = function (options) { if (options) { var redraw = false; if (this.options.orientation != options.orientation && options.orientation !== undefined) { @@ -100,7 +99,7 @@ DataAxis.prototype.setOptions = function(options) { this.show(); } } -} +}; /** @@ -125,14 +124,15 @@ DataAxis.prototype._create = function() { this.dom.frame.appendChild(this.svg); }; -DataAxis.prototype._redrawGroupIcons = function() { - SVGutil._prepareSVGElements(this.svgContainer); +DataAxis.prototype._redrawGroupIcons = function () { + DOMutil.prepareElements(this.svgElements); var x; var iconWidth = this.options.iconWidth; var iconHeight = 15; var iconOffset = 4; var y = iconOffset + 0.5 * iconHeight; + if (this.options.orientation == 'left') { x = iconOffset; } @@ -147,8 +147,8 @@ DataAxis.prototype._redrawGroupIcons = function() { } } - SVGutil._cleanupSVGElements(this.svgContainer); -} + DOMutil.cleanupElements(this.svgElements); +}; /** * Create the HTML DOM for the DataAxis @@ -183,14 +183,13 @@ DataAxis.prototype.hide = function() { /** * Set a range (start and end) - * @param {Range | Object} range A Range or an object containing start and end. + * @param end + * @param start + * @param end */ -DataAxis.prototype.setRange = function (range) { - if (!(range instanceof Range) && (!range || range.start === undefined || range.end === undefined)) { - throw new TypeError('Range must be an instance of Range, ' + 'or an object containing start and end.'); - } - this.yRange.start = range.start; - this.yRange.end = range.end; +DataAxis.prototype.setRange = function (start, end) { + this.yRange.start = start; + this.yRange.end = end; }; /** @@ -217,7 +216,7 @@ DataAxis.prototype.redraw = function () { props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2*this.options.minorLinesOffset; props.minorLineHeight = 1; - props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2*this.options.majorLinesOffset;; + props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset; props.majorLineHeight = 1; // take frame offline while updating (is almost twice as fast) @@ -247,26 +246,19 @@ DataAxis.prototype.redraw = function () { * @private */ DataAxis.prototype._redrawLabels = function () { + DOMutil.prepareElements(this.DOMelements); + var orientation = this.options['orientation']; // calculate range and step (step such that we have space for 7 characters per label) var start = this.yRange.start; var end = this.yRange.end; - var minimumStep = (this.props.minorCharHeight || 10); //in pixels + var minimumStep = (this.props.majorCharHeight || 10); //in pixels var step = new DataStep(start, end, minimumStep, this.dom.frame.offsetHeight); this.step = step; step.first(); - - // Move all DOM elements to a "redundant" list, where they - // can be picked for re-use, and clear the lists with lines and texts. - // At the end of the function _redrawLabels, left over elements will be cleaned up - var dom = this.dom; - dom.redundant.lines = dom.lines; - dom.redundant.labels = dom.labels; - dom.lines = []; - dom.labels = []; - + // get the distance in pixels for a step var stepPixels = this.dom.frame.offsetHeight / ((step.marginRange / step.step) + 1); this.stepPixels = stepPixels; @@ -282,7 +274,6 @@ DataAxis.prototype._redrawLabels = function () { amountOfSteps = this.height / stepPixels; } - var xFirstMajorLabel = undefined; this.valueAtZero = step.marginEnd; var marginStartPos = 0; @@ -297,21 +288,18 @@ DataAxis.prototype._redrawLabels = function () { marginStartPos = max * stepPixels; var isMajor = step.isMajor(); - if (this.options['showMinorLabels'] && isMajor == false) { + if (this.options['showMinorLabels'] && isMajor == false || this.master == false) { this._redrawLabel(y - 2, step.current, orientation, 'yAxis minor', this.props.minorCharHeight); } - if (isMajor && this.options['showMajorLabels']) { + if (isMajor && this.options['showMajorLabels'] && this.master == true) { if (y >= 0) { - if (xFirstMajorLabel == undefined) { - xFirstMajorLabel = y; - } this._redrawLabel(y - 2, step.current, orientation, 'yAxis major', this.props.majorCharHeight); } - this._redrawMajorLine(y, orientation); + this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth); } else { - this._redrawMinorLine(y, orientation); + this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth); } step.next(); @@ -330,55 +318,23 @@ DataAxis.prototype._redrawLabels = function () { this.conversionFactor = marginStartPos/((amountOfSteps-1) * step.step); - // Cleanup leftover DOM elements from the redundant list - util.forEach(this.dom.redundant, function (arr) { - while (arr.length) { - var elem = arr.pop(); - if (elem && elem.parentNode) { - elem.parentNode.removeChild(elem); - } - } - }); + DOMutil.cleanupElements(this.DOMelements); }; -DataAxis.prototype.convertValues = function(data) { - for (var i = 0; i < data.length; i++) { - data[i].y = this.convertValue(data[i].y); - } - return data; -} - -DataAxis.prototype.convertValue = function(value) { - var invertedValue = this.valueAtZero - value; - var convertedValue = invertedValue * this.conversionFactor; - return convertedValue; // the -2 is to compensate for the borders -} - - - /** * Create a label for the axis at position x - * @param {Number} x - * @param {String} text - * @param {String} orientation "top" or "bottom" (default) * @private + * @param y + * @param text + * @param orientation + * @param className + * @param characterHeight */ DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) { // reuse redundant label - var label = this.dom.redundant.labels.shift(); - - if (!label) { - // create label - var content = document.createTextNode(text); - label = document.createElement('div'); - label.className = className; - label.appendChild(content); - this.dom.frame.appendChild(label); - } - this.dom.labels.push(label); - - label.childNodes[0].nodeValue = text; - //label.title = title; // TODO: this is a heavy operation + var label = DOMutil.getDOMElement('div',this.DOMelements, this.dom.frame); //this.dom.redundant.labels.shift(); + label.className = className; + label.innerHTML = text; if (orientation == 'left') { label.style.left = '-' + this.options.labelOffsetX + 'px'; @@ -397,70 +353,40 @@ DataAxis.prototype._redrawLabel = function (y, text, orientation, className, cha if (this.maxLabelSize < text.length * largestWidth) { this.maxLabelSize = text.length * largestWidth; } - - }; /** * Create a minor line for the axis at position y - * @param {Number} y - * @param {String} orientation "top" or "bottom" (default) - * @private + * @param y + * @param orientation + * @param className + * @param offset + * @param width */ -DataAxis.prototype._redrawMinorLine = function (y, orientation) { +DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) { if (this.master == true) { // reuse redundant line - var line = this.dom.redundant.lines.shift(); - - if (!line) { - // create vertical line - line = document.createElement('div'); - line.className = 'grid horizontal minor'; - this.dom.lineContainer.appendChild(line); - } - this.dom.lines.push(line); + var line = DOMutil.getDOMElement('div',this.DOMelements, this.dom.lineContainer);//this.dom.redundant.lines.shift(); + line.className = className; + line.innerHTML = ''; - var props = this.props; if (orientation == 'left') { - line.style.left = (this.width - this.options.minorLinesOffset) + 'px'; + line.style.left = (this.width - offset) + 'px'; } else { - line.style.left = -1*(this.width - this.options.minorLinesOffset) + 'px'; + line.style.left = -1*(this.width - offset) + 'px'; } - line.style.width = props.minorLineWidth + 'px'; + line.style.width = width + 'px'; line.style.top = y + 'px'; } }; -/** - * Create a Major line for the axis at position x - * @param {Number} x - * @param {String} orientation "top" or "bottom" (default) - * @private - */ -DataAxis.prototype._redrawMajorLine = function (y, orientation) { - if (this.master == true) { - // reuse redundant line - var line = this.dom.redundant.lines.shift(); - if (!line) { - // create vertical line - line = document.createElement('div'); - line.className = 'grid horizontal major'; - this.dom.lineContainer.appendChild(line); - } - this.dom.lines.push(line); - - if (orientation == 'left') { - line.style.left = (this.width - this.options.majorLinesOffset) + 'px'; - } - else { - line.style.left = -1*(this.width - this.options.majorLinesOffset) + 'px'; - } - line.style.top = y + 'px'; - line.style.width = this.props.majorLineWidth + 'px'; - } +DataAxis.prototype.convertValue = function (value) { + var invertedValue = this.valueAtZero - value; + var convertedValue = invertedValue * this.conversionFactor; + return convertedValue; // the -2 is to compensate for the borders }; diff --git a/src/timeline/component/GraphGroup.js b/src/timeline/component/GraphGroup.js index 9f88f985..1a0dbae3 100644 --- a/src/timeline/component/GraphGroup.js +++ b/src/timeline/component/GraphGroup.js @@ -39,20 +39,21 @@ GraphGroup.prototype.update = function(group) { GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, iconWidth, iconHeight) { var fillHeight = iconHeight * 0.5; - var path, fillPath, outline; - if (this.options.style == 'line') { - outline = SVGutil._getSVGElement("rect", JSONcontainer, SVGcontainer); - outline.setAttributeNS(null, "x", x); - outline.setAttributeNS(null, "y", y - fillHeight); - outline.setAttributeNS(null, "width", iconWidth); - outline.setAttributeNS(null, "height", 2*fillHeight); - outline.setAttributeNS(null, "class", "outline"); + var path, fillPath; + + var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer); + outline.setAttributeNS(null, "x", x); + outline.setAttributeNS(null, "y", y - fillHeight); + outline.setAttributeNS(null, "width", iconWidth); + outline.setAttributeNS(null, "height", 2*fillHeight); + outline.setAttributeNS(null, "class", "outline"); - path = SVGutil._getSVGElement("path", JSONcontainer, SVGcontainer); + if (this.options.style == 'line') { + path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer); path.setAttributeNS(null, "class", this.className); path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+""); if (this.options.shaded.enabled == true) { - fillPath = SVGutil._getSVGElement("path", JSONcontainer, SVGcontainer); + fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer); if (this.options.shaded.orientation == 'top') { fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) + "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight)); @@ -67,11 +68,17 @@ GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, iconWi } if (this.options.drawPoints.enabled == true) { - SVGutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer); + DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer); } } else { - console.log("bar") - //TODO: bars + var barWidth = Math.round(0.3 * iconWidth); + var bar1Height = Math.round(0.4 * iconHeight); + var bar2Height = Math.round(0.75 * iconHeight); + + var offset = Math.round((iconWidth - (2 * barWidth))/3); + + DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer); + DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer); } } diff --git a/src/timeline/component/Legend.js b/src/timeline/component/Legend.js index 14c1a818..6b6f0afd 100644 --- a/src/timeline/component/Legend.js +++ b/src/timeline/component/Legend.js @@ -94,7 +94,7 @@ Legend.prototype.redraw = function() { } Legend.prototype.drawLegend = function() { - this.linegraph._prepareSVGElements.call(this,this.svgElements); + this.linegraph.prepareElements.call(this,this.svgElements); var x = 0; var y = 0; var lineLength = 30; @@ -104,7 +104,7 @@ Legend.prototype.drawLegend = function() { var legendWidth = 298; var padding = 5; - var border = this._getSVGElement("rect", this.svgLegendElements, this.svgLegend); + var border = this.getSVGElement("rect", this.svgLegendElements, this.svgLegend); border.setAttributeNS(null, "x", x); border.setAttributeNS(null, "y", y); border.setAttributeNS(null, "width", legendWidth); @@ -115,18 +115,18 @@ Legend.prototype.drawLegend = function() { if (classes.length > 0) { for (var i = 0; i < classes.length; i++) { - outline = this._getSVGElement("rect", this.svgLegendElements, this.svgLegend); + 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 = 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); + 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)); @@ -152,5 +152,5 @@ Legend.prototype.drawLegend = function() { - this._cleanupSVGElements(this.svgLegendElements); + this.cleanupElements(this.svgLegendElements); } \ No newline at end of file diff --git a/src/timeline/component/Linegraph.js b/src/timeline/component/Linegraph.js index efa84355..e21f4130 100644 --- a/src/timeline/component/Linegraph.js +++ b/src/timeline/component/Linegraph.js @@ -97,7 +97,6 @@ function Linegraph(body, options) { this.setOptions(options); this.groupsUsingDefaultStyles = [0]; - var me = this; this.body.emitter.on("rangechange",function() { if (me.lastStart != 0) { var offset = me.body.range.start - me.lastStart; @@ -315,27 +314,7 @@ Linegraph.prototype._onRemove = 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.groupsUsingDefaultStyles); - this.legend.addGroup(groupIds[i],this.groups[groupIds[i]]); - - if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { - this.yAxisRight.addGroup(groupIds[i], this.groups[groupIds[i]]); - } - else { - this.yAxisLeft.addGroup(groupIds[i], this.groups[groupIds[i]]); - } - } - else { - this.groups[groupIds[i]].update(group); - this.legend.updateGroup(groupIds[i],this.groups[groupIds[i]]); - if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { - this.yAxisRight.updateGroup(groupIds[i], this.groups[groupIds[i]]); - } - else { - this.yAxisLeft.updateGroup(groupIds[i], this.groups[groupIds[i]]); - } - } + this._updateGroup(group, groupIds[i]); } this._updateUngrouped(); this._updateGraph(); @@ -352,34 +331,40 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) { this.redraw(); }; -/** - * Create or delete the group holding all ungrouped items. This group is used when - * there are no groups specified. This anonymous group is called 'graph'. - * @protected - */ -Linegraph.prototype._updateUngrouped = function() { - var group = {id: UNGROUPED, content: "graph"}; - if (!this.groups.hasOwnProperty(UNGROUPED)) { - this.groups[UNGROUPED] = new GraphGroup(group, this.options, this.groupsUsingDefaultStyles); - this.legend.addGroup(UNGROUPED,this.groups[UNGROUPED]); - if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { - this.yAxisRight.addGroup(UNGROUPED, this.groups[UNGROUPED]); +Linegraph.prototype._updateGroup = function (group, groupId) { + + if (!this.groups.hasOwnProperty(groupId)) { + this.groups[groupId] = new GraphGroup(group, this.options, this.groupsUsingDefaultStyles); + this.legend.addGroup(groupId, this.groups[groupId]); + + if (this.groups[groupId].options.yAxisOrientation == 'right') { + this.yAxisRight.addGroup(groupId, this.groups[groupId]); } else { - this.yAxisLeft.addGroup(UNGROUPED, this.groups[UNGROUPED]); + this.yAxisLeft.addGroup(groupId, this.groups[groupId]); } } else { - this.groups[UNGROUPED].update(group); - this.legend.updateGroup(UNGROUPED,this.groups[UNGROUPED]); - if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { - this.yAxisRight.updateGroup(UNGROUPED, this.groups[UNGROUPED]); + this.groups[groupId].update(group); + this.legend.updateGroup(groupId, this.groups[groupId]); + if (this.groups[groupId].options.yAxisOrientation == 'right') { + this.yAxisRight.updateGroup(groupId, this.groups[groupId]); } else { - this.yAxisLeft.updateGroup(UNGROUPED, this.groups[UNGROUPED]); + this.yAxisLeft.updateGroup(groupId, this.groups[groupId]); } } +}; + +/** + * Create or delete the group holding all ungrouped items. This group is used when + * there are no groups specified. This anonymous group is called 'graph'. + * @protected + */ +Linegraph.prototype._updateUngrouped = function() { + var group = {id: UNGROUPED, content: "graph"}; + this._updateGroup(group, UNGROUPED); if (this.itemsData != null) { var datapoints = this.itemsData.get({ @@ -444,7 +429,7 @@ Linegraph.prototype.redraw = function() { */ Linegraph.prototype._updateGraph = function () { // reset the svg elements - SVGutil._prepareSVGElements(this.svgElements); + DOMutil.prepareElements(this.svgElements); if (this.width != 0 && this.itemsData != null) { // look at different lines @@ -457,10 +442,8 @@ Linegraph.prototype._updateGraph = function () { } } - // this.legend.redraw(); - // cleanup unused svg elements - SVGutil._cleanupSVGElements(this.svgElements); + DOMutil.cleanupElements(this.svgElements); }; /** @@ -468,7 +451,7 @@ Linegraph.prototype._updateGraph = function () { * @param {array} groupIds * @private */ -Linegraph.prototype._updateYAxis = function(groupIds) { +Linegraph.prototype._updateYAxis = function (groupIds) { var yAxisLeftUsed = false; var yAxisRightUsed = false; var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal; @@ -484,28 +467,38 @@ Linegraph.prototype._updateYAxis = function(groupIds) { orientation = 'right'; } - var view = new vis.DataSet(this.itemsData.get({filter: function (item) {return item.group == groupIds[i];}})); + var view = new vis.DataSet(this.itemsData.get({filter: function (item) { + return item.group == groupIds[i]; + }})); minVal = view.min("y").y; maxVal = view.max("y").y; if (orientation == 'left') { yAxisLeftUsed = true; - if (minLeft > minVal) {minLeft = minVal;} - if (maxLeft < maxVal) {maxLeft = maxVal;} + if (minLeft > minVal) { + minLeft = minVal; + } + if (maxLeft < maxVal) { + maxLeft = maxVal; + } } else { yAxisRightUsed = true; - if (minRight > minVal) {minRight = minVal;} - if (maxRight < maxVal) {maxRight = maxVal;} + if (minRight > minVal) { + minRight = minVal; + } + if (maxRight < maxVal) { + maxRight = maxVal; + } } delete view; } - if (yAxisLeftUsed == true) { - this.yAxisLeft.setRange({start: minLeft, end: maxLeft}); + if (yAxisLeftUsed == true) { + this.yAxisLeft.setRange(minLeft, maxLeft); } if (yAxisRightUsed == true) { - this.yAxisRight.setRange({start: minRight, end: maxRight}); + this.yAxisRight.setRange(minRight, maxRight); } } @@ -516,11 +509,11 @@ Linegraph.prototype._updateYAxis = function(groupIds) { } if (yAxisRightUsed == true && yAxisLeftUsed == true) { - this.yAxisLeft.drawIcons = true; + this.yAxisLeft.drawIcons = true; this.yAxisRight.drawIcons = true; } else { - this.yAxisLeft.drawIcons = false; + this.yAxisLeft.drawIcons = false; this.yAxisRight.drawIcons = false; } @@ -537,17 +530,17 @@ Linegraph.prototype._updateYAxis = function(groupIds) { else { this.yAxisRight.redraw(); } -} +}; /** * 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 + * @param axis */ -Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) { +Linegraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) { var changed = false; if (axisUsed == false) { if (axis.dom.frame.parentNode) { @@ -562,7 +555,7 @@ Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) { } } return changed; -} +}; /** @@ -582,24 +575,30 @@ Linegraph.prototype._drawGraph = function (groupId, groupIndex, amountOfGraphs) this._drawLineGraph(datapoints, group); } else { - this._drawBarGraph(datapoints, group, amountOfGraphs); + this._drawBarGraph(datapoints, group); } }; /** * draw a bar graph * @param datapoints - * @param options - * @param amountOfGraphs + * @param group */ -Linegraph.prototype._drawBarGraph = function (datapoints, group, amountOfGraphs) { +Linegraph.prototype._drawBarGraph = function (datapoints, group) { if (datapoints != null) { if (datapoints.length > 0) { var dataset = this._prepareData(datapoints, group.options); - var bar; - console.log(group.options); + var width, coreDistance; + var minWidth = 0.1 * group.options.barChart.width; + for (var i = 0; i < dataset.length; i++) { - this._drawBar(dataset[i].x, dataset[i].y, group.options.barChart.width, this.zeroPosition - dataset[i].y, group.className + ' bar'); + width = group.options.barChart.width; + // dynammically downscale the width so there is no overlap up to 1/10th the original width + if (i+1 < dataset.length) {coreDistance = Math.abs(dataset[i+1].x - dataset[i].x);} + if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(dataset[i-1].x - dataset[i].x));} + if (coreDistance < width) {width = coreDistance < minWidth ? minWidth : coreDistance;} + + DOMutil.drawBar(dataset[i].x, dataset[i].y, width, this.zeroPosition - dataset[i].y, group.className + ' bar', this.svgElements, this.svg); } // draw points @@ -610,29 +609,12 @@ Linegraph.prototype._drawBarGraph = function (datapoints, group, amountOfGraphs) } }; -/** - * draw a bar SVG element - * - * @param x - * @param y - * @param className - */ -Linegraph.prototype._drawBar = function (x, y, width, height, className) { - rect = SVGutil._getSVGElement('rect',this.svgElements, this.svg); - - rect.setAttributeNS(null, "x", x - 0.5 * width); - rect.setAttributeNS(null, "y", y); - rect.setAttributeNS(null, "width", width); - rect.setAttributeNS(null, "height", height); - rect.setAttributeNS(null, "class", className); -}; - /** * draw a line graph - * + * * @param datapoints - * @param options + * @param group */ Linegraph.prototype._drawLineGraph = function (datapoints, group) { if (datapoints != null) { @@ -640,7 +622,7 @@ Linegraph.prototype._drawLineGraph = function (datapoints, group) { var dataset = this._prepareData(datapoints, group.options); var path, d; - path = SVGutil._getSVGElement('path', this.svgElements, this.svg); + path = DOMutil.getSVGElement('path', this.svgElements, this.svg); path.setAttributeNS(null, "class", group.className); @@ -654,12 +636,13 @@ Linegraph.prototype._drawLineGraph = function (datapoints, group) { // append with points for fill and finalize the path if (group.options.shaded.enabled == true) { - var fillPath = SVGutil._getSVGElement('path',this.svgElements, this.svg); + var fillPath = DOMutil.getSVGElement('path',this.svgElements, this.svg); + var dFill; if (group.options.shaded.orientation == 'top') { - var dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0; + 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; + dFill = "M" + dataset[0].x + "," + this.svg.offsetHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + this.svg.offsetHeight; } fillPath.setAttributeNS(null, "class", group.className + " fill"); fillPath.setAttributeNS(null, "d", dFill); @@ -677,15 +660,15 @@ Linegraph.prototype._drawLineGraph = function (datapoints, group) { /** * draw the data points - * + * * @param dataset - * @param options * @param JSONcontainer * @param svg + * @param group */ Linegraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg) { for (var i = 0; i < dataset.length; i++) { - SVGutil.drawPoint(dataset[i].x, dataset[i].y, group, JSONcontainer, svg); + DOMutil.drawPoint(dataset[i].x, dataset[i].y, group, JSONcontainer, svg); } }; @@ -711,7 +694,7 @@ Linegraph.prototype._prepareData = function (datapoints, options) { axis = this.yAxisRight; } for (var i = 0; i < datapoints.length; i++) { - xValue = toScreen(datapoints[i].x) + this.width; + xValue = toScreen(datapoints[i].x) + this.width - 1; yValue = axis.convertValue(datapoints[i].y); extractedData.push({x: xValue, y: yValue}); } diff --git a/src/timeline/component/css/pathStyles.css b/src/timeline/component/css/pathStyles.css index 77c6d609..ec0545e7 100644 --- a/src/timeline/component/css/pathStyles.css +++ b/src/timeline/component/css/pathStyles.css @@ -76,6 +76,7 @@ .vis.timeline .bar { fill-opacity:0.7; + stroke-width:1px; } .vis.timeline .point {