From b6e5a9dadb1f98f4d32937afb2c28346438eab7d Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Fri, 20 Jun 2014 18:53:18 +0200 Subject: [PATCH] bargraphs!! woohoo!! --- Jakefile.js | 1 + dist/vis.css | 5 + dist/vis.js | 427 ++++++++++++---------- examples/graph/02_random_nodes.html | 2 +- examples/graph2d/01_basic.html | 3 +- src/svgUtil.js | 107 ++++++ src/timeline/component/DataAxis.js | 8 +- src/timeline/component/GraphGroup.js | 44 +-- src/timeline/component/Legend.js | 2 +- src/timeline/component/Linegraph.js | 237 ++++-------- src/timeline/component/css/pathStyles.css | 5 + src/util.js | 26 ++ 12 files changed, 471 insertions(+), 396 deletions(-) create mode 100644 src/svgUtil.js diff --git a/Jakefile.js b/Jakefile.js index 8e84f5f0..360c59e9 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -64,6 +64,7 @@ task('build', {async: true}, function () { './src/shim.js', './src/util.js', + './src/SVGutil.js', './src/DataSet.js', './src/DataView.js', diff --git a/dist/vis.css b/dist/vis.css index b2d1edc3..efe123cf 100644 --- a/dist/vis.css +++ b/dist/vis.css @@ -447,6 +447,11 @@ stroke: none; } + +.vis.timeline .bar { + fill-opacity:0.7; +} + .vis.timeline .point { stroke-width:2px; fill-opacity:1.0; diff --git a/dist/vis.js b/dist/vis.js index 84e9802a..e00e1929 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -4,8 +4,8 @@ * * A dynamic, browser-based visualization library. * - * @version 1.1.0 - * @date 2014-06-20 + * @version @@version + * @date @@date * * @license * Copyright (C) 2011-2014 Almende B.V, http://almende.com @@ -1341,6 +1341,138 @@ util.copyObject = function(objectFrom, objectTo) { return objectTo; }; + +/** + * 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 + */ +util._mergeOptions = function (mergeTarget, options, option) { + if (options[option]) { + if (typeof options[option] == 'boolean') { + mergeTarget[option].enabled = options[option]; + } + else { + mergeTarget[option].enabled = true; + for (prop in options[option]) { + if (options[option].hasOwnProperty(prop)) { + mergeTarget[option][prop] = options[option][prop]; + } + } + } + } +} +/** + * Created by Alex on 6/20/14. + */ + +var SVGutil = {} +/** + * this prepares the JSON container for allocating SVG elements + * @param JSONcontainer + * @private + */ +SVGutil._prepareSVGElements = function(JSONcontainer) { + // cleanup the redundant svgElements; + for (var elementType in JSONcontainer) { + if (JSONcontainer.hasOwnProperty(elementType)) { + JSONcontainer[elementType].redundant = JSONcontainer[elementType].used; + JSONcontainer[elementType].used = []; + } + } +}; + +/** + * 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 + */ +SVGutil._cleanupSVGElements = function(JSONcontainer) { + // cleanup the redundant svgElements; + 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]); + } + JSONcontainer[elementType].redundant = []; + } + } +}; + +/** + * 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 + */ +SVGutil._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 + // 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.createElementNS('http://www.w3.org/2000/svg', elementType); + svgContainer.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.createElementNS('http://www.w3.org/2000/svg', elementType); + JSONcontainer[elementType] = {used: [], redundant: []}; + svgContainer.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. + * 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 {*} + */ +SVGutil.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { + var point; + if (group.options.drawPoints.style == 'circle') { + point = SVGutil._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.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; +} /** * DataSet * @@ -2568,14 +2700,14 @@ DataView.prototype.unsubscribe = DataView.prototype.off; * @param {Object} data * @param {ItemSet} itemSet */ -function GraphGroup (group, options, linegraph) { - var fields = ['yAxisOrientation','barGraph','drawPoints','catmullRom'] +function GraphGroup (group, options, groupsUsingDefaultStyles) { + var fields = ['style','yAxisOrientation','barChart','drawPoints','shaded','catmullRom'] this.options = util.selectiveDeepExtend(fields,{},options); - this.linegraph = linegraph; this.usingDefaultStyle = group.className === undefined; + this.groupsUsingDefaultStyles = groupsUsingDefaultStyles; this.update(group); if (this.usingDefaultStyle == true) { - this.linegraph.groupsUsingDefaultStyles += 1; + this.groupsUsingDefaultStyles[0] += 1; } } @@ -2585,55 +2717,57 @@ GraphGroup.prototype.setClass = function (className) { GraphGroup.prototype.setOptions = function(options) { if (options !== undefined) { - var fields = ['yAxisOrientation']; - util.selectiveExtend(fields, this.options, options); - this.linegraph._mergeOptions(this.options, options,'catmullRom'); - this.linegraph._mergeOptions(this.options, options,'drawPoints'); - this.linegraph._mergeOptions(this.options, options,'shaded'); + var fields = ['yAxisOrientation','style','barChart']; + util.selectiveDeepExtend(fields, this.options, options); + + util._mergeOptions(this.options, options,'catmullRom'); + util._mergeOptions(this.options, options,'drawPoints'); + util._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.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0]; this.setOptions(group.options); }; -GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, lineLength, iconHeight) { +GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, iconWidth, iconHeight) { var fillHeight = iconHeight * 0.5; var path, fillPath, outline; - if (this.options.barGraph.enabled == false) { - outline = this.linegraph._getSVGElement("rect", JSONcontainer, SVGcontainer); + 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", lineLength); + outline.setAttributeNS(null, "width", iconWidth); outline.setAttributeNS(null, "height", 2*fillHeight); outline.setAttributeNS(null, "class", "outline"); - path = this.linegraph._getSVGElement("path", JSONcontainer, SVGcontainer); + path = SVGutil._getSVGElement("path", JSONcontainer, SVGcontainer); path.setAttributeNS(null, "class", this.className); - path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + lineLength) + ","+y+""); + path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+""); if (this.options.shaded.enabled == true) { - fillPath = this.linegraph._getSVGElement("path", JSONcontainer, SVGcontainer); + fillPath = SVGutil._getSVGElement("path", JSONcontainer, SVGcontainer); 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)); + "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight)); } else { fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " + "L"+x+"," + (y + fillHeight) + " " + - "L"+ (x + lineLength) + "," + (y + fillHeight) + - "L"+ (x + lineLength) + ","+y); + "L"+ (x + iconWidth) + "," + (y + fillHeight) + + "L"+ (x + iconWidth) + ","+y); } fillPath.setAttributeNS(null, "class", this.className + " iconFill"); } if (this.options.drawPoints.enabled == true) { - this.linegraph.drawPoint(x + 0.5 * lineLength,y, this, JSONcontainer, SVGcontainer); + SVGutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer); } } else { + console.log("bar") //TODO: bars } } @@ -2780,7 +2914,7 @@ Legend.prototype.drawLegend = function() { fillPath.setAttributeNS(null, "class", classes[i] + " fill"); } - if (this.options.drawPoints.enabled == true) { + if (this.options._drawPoints.enabled == true) { this.drawPoint(x + 0.5 * lineLength,y,classes[i], this.svgLegendElements, this.svgLegend); } y += spacing; @@ -2922,6 +3056,8 @@ DataAxis.prototype._create = function() { }; DataAxis.prototype._redrawGroupIcons = function() { + SVGutil._prepareSVGElements(this.svgContainer); + var x; var iconWidth = this.options.iconWidth; var iconHeight = 15; @@ -2940,6 +3076,8 @@ DataAxis.prototype._redrawGroupIcons = function() { y += iconHeight + iconOffset; } } + + SVGutil._cleanupSVGElements(this.svgContainer); } /** @@ -3185,7 +3323,7 @@ DataAxis.prototype._redrawLabel = function (y, text, orientation, className, cha text += ''; - var largestWidth = this.props.majorCharWidth > this.props.minorCharWidth ? this.props.majorCharWidth : this.props.minorCharWidth; + var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth); if (this.maxLabelSize < text.length * largestWidth) { this.maxLabelSize = text.length * largestWidth; } @@ -3301,8 +3439,6 @@ DataAxis.prototype.snap = function(date) { return this.step.snap(date); }; - - var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items function Linegraph(body, options) { @@ -3315,9 +3451,9 @@ function Linegraph(body, options) { enabled: true, orientation: 'top' // top, bottom }, - barGraph: { - enabled: false, - binSize: 'auto' + style: 'line', // line, bar + barChart: { + width: 50 }, drawPoints: { enabled: true, @@ -3341,9 +3477,18 @@ function Linegraph(body, options) { visible:true }, legend: { - orientation: 'left', // left, right - position: 'left', // left, center, right - visible: true + enabled: true, + axisIcons: true, + left: { + visible: true, + position: 'top-left', // top/bottom - left,center,right + textAlign: 'left' + }, + right: { + visible: true, + position: 'top-left', // top/bottom - left,center,right + textAlign: 'right' + } } }; @@ -3391,8 +3536,7 @@ function Linegraph(body, options) { this.svgElements = {}; this.setOptions(options); - this.groupsUsingDefaultStyles = 0; - + this.groupsUsingDefaultStyles = [0]; var me = this; this.body.emitter.on("rangechange",function() { @@ -3409,7 +3553,7 @@ function Linegraph(body, options) { this.body.emitter.on("rangechanged", function() { me.lastStart = me.body.range.start; me.svg.style.left = util.option.asSize(-me.width); - me.updateGraph.apply(me); + me._updateGraph.apply(me); }); // create the HTML DOM @@ -3458,7 +3602,7 @@ Linegraph.prototype._create = function(){ */ Linegraph.prototype.setOptions = function(options) { if (options) { - var fields = ['yAxisOrientation','dataAxis','legend']; + var fields = ['yAxisOrientation','style','barChart','dataAxis','legend']; util.selectiveDeepExtend(fields, this.options, options); if (options.catmullRom) { @@ -3478,38 +3622,12 @@ Linegraph.prototype.setOptions = function(options) { } } - console.log("OPTIONS:",this.options , options); - this._mergeOptions(this.options, options,'catmullRom'); - this._mergeOptions(this.options, options,'drawPoints'); - this._mergeOptions(this.options, options,'shaded'); + util._mergeOptions(this.options, options,'catmullRom'); + util._mergeOptions(this.options, options,'drawPoints'); + util._mergeOptions(this.options, options,'shaded'); } }; -/** - * 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') { - mergeTarget[option].enabled = options[option]; - } - else { - mergeTarget[option].enabled = true; - for (prop in options[option]) { - if (options[option].hasOwnProperty(prop)) { - mergeTarget[option][prop] = options[option][prop]; - } - } - } - } -} - /** * Hide the component from the DOM */ @@ -3575,7 +3693,7 @@ Linegraph.prototype.setItems = function(items) { this._onAdd(ids); } this._updateUngrouped(); - this.updateGraph(); + this._updateGraph(); this.redraw(); }; @@ -3622,7 +3740,7 @@ Linegraph.prototype.setGroups = function(groups) { this._onAddGroups(ids); } this._updateUngrouped(); - this.updateGraph(); + this._updateGraph(); this.redraw(); }; @@ -3630,7 +3748,7 @@ Linegraph.prototype.setGroups = function(groups) { Linegraph.prototype._onUpdate = function(ids) { this._updateUngrouped(); - this.updateGraph(); + this._updateGraph(); this.redraw(); }; Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate; @@ -3639,8 +3757,9 @@ 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.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]]); } @@ -3659,7 +3778,8 @@ Linegraph.prototype._onUpdateGroups = function (groupIds) { } } } - this.updateGraph(); + this._updateUngrouped(); + this._updateGraph(); this.redraw(); }; Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdateGroups; @@ -3668,7 +3788,8 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) { for (var i = 0; i < groupIds.length; i++) { this.legend.removeGroup(groupIds[i]); } - this.updateGraph(); + this._updateUngrouped(); + this._updateGraph(); this.redraw(); }; @@ -3678,10 +3799,11 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) { * @protected */ Linegraph.prototype._updateUngrouped = function() { - var group = {content: "graph"}; + var group = {id: UNGROUPED, content: "graph"}; if (!this.groups.hasOwnProperty(UNGROUPED)) { - this.groups[UNGROUPED] = new GraphGroup(group, this.options, this); + 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]); } @@ -3752,7 +3874,7 @@ Linegraph.prototype.redraw = function() { this.svg.style.left = util.option.asSize(-this.width); } if (zoomed == true) { - this.updateGraph(); + this._updateGraph(); } return resized; }; @@ -3761,9 +3883,9 @@ Linegraph.prototype.redraw = function() { * Update and redraw the graph. * */ -Linegraph.prototype.updateGraph = function () { +Linegraph.prototype._updateGraph = function () { // reset the svg elements - this._prepareSVGElements(this.svgElements); + SVGutil._prepareSVGElements(this.svgElements); if (this.width != 0 && this.itemsData != null) { // look at different lines @@ -3771,15 +3893,15 @@ Linegraph.prototype.updateGraph = function () { if (groupIds.length > 0) { this._updateYAxis(groupIds); for (var i = 0; i < groupIds.length; i++) { - this.drawGraph(groupIds[i], i, groupIds.length); + this._drawGraph(groupIds[i], i, groupIds.length); } } } -// this.legend.redraw(); + // this.legend.redraw(); // cleanup unused svg elements - this._cleanupSVGElements(this.svgElements); + SVGutil._cleanupSVGElements(this.svgElements); }; /** @@ -3891,17 +4013,17 @@ Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) { * @param groupIndex * @param amountOfGraphs */ -Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) { +Linegraph.prototype._drawGraph = function (groupId, groupIndex, amountOfGraphs) { var datapoints = this.itemsData.get({filter: function (item) {return item.group == groupId;}, type: {x:"Date"}}); // can be optimized, only has to be done once. var group = this.groups[groupId]; - if (this.options.barGraph.enabled == 'true') { - this.drawBarGraph(datapoints, group, amountOfGraphs); + if (group.options.style == 'line') { + this._drawLineGraph(datapoints, group); } else { - this.drawLineGraph(datapoints, group); + this._drawBarGraph(datapoints, group, amountOfGraphs); } }; @@ -3911,13 +4033,19 @@ Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) { * @param options * @param amountOfGraphs */ -Linegraph.prototype.drawBarGraph = function (datapoints, group, amountOfGraphs) { +Linegraph.prototype._drawBarGraph = function (datapoints, group, amountOfGraphs) { if (datapoints != null) { if (datapoints.length > 0) { - var dataset = this._prepareData(datapoints); - // draw points + var dataset = this._prepareData(datapoints, group.options); + var bar; + console.log(group.options); for (var i = 0; i < dataset.length; i++) { - this.drawBar(dataset[i].x, dataset[i].y, className); + this._drawBar(dataset[i].x, dataset[i].y, group.options.barChart.width, this.zeroPosition - dataset[i].y, group.className + ' bar'); + } + + // draw points + if (group.options.drawPoints.enabled == true) { + this._drawPoints(dataset, group, this.svgElements, this.svg); } } } @@ -3930,15 +4058,14 @@ Linegraph.prototype.drawBarGraph = function (datapoints, group, amountOfGraphs) * @param y * @param className */ -Linegraph.prototype.drawBar = function (x, y, className) { - var width = 10; - rect = this._getSVGElement('rect',this.svgElements, this.svg); +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", this.svg.offsetHeight - y); - rect.setAttributeNS(null, "class", className + " point"); + rect.setAttributeNS(null, "height", height); + rect.setAttributeNS(null, "class", className); }; @@ -3948,13 +4075,13 @@ Linegraph.prototype.drawBar = function (x, y, className) { * @param datapoints * @param options */ -Linegraph.prototype.drawLineGraph = function (datapoints, group) { +Linegraph.prototype._drawLineGraph = function (datapoints, group) { if (datapoints != null) { if (datapoints.length > 0) { var dataset = this._prepareData(datapoints, group.options); var path, d; - path = this._getSVGElement('path', this.svgElements, this.svg); + path = SVGutil._getSVGElement('path', this.svgElements, this.svg); path.setAttributeNS(null, "class", group.className); @@ -3968,7 +4095,7 @@ Linegraph.prototype.drawLineGraph = function (datapoints, group) { // append with points for fill and finalize the path if (group.options.shaded.enabled == true) { - var fillPath = this._getSVGElement('path',this.svgElements, this.svg); + var fillPath = SVGutil._getSVGElement('path',this.svgElements, this.svg); if (group.options.shaded.orientation == 'top') { var dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0; } @@ -3983,7 +4110,7 @@ Linegraph.prototype.drawLineGraph = function (datapoints, group) { // draw points if (group.options.drawPoints.enabled == true) { - this.drawPoints(dataset, group, this.svgElements, this.svg); + this._drawPoints(dataset, group, this.svgElements, this.svg); } } } @@ -3997,112 +4124,13 @@ Linegraph.prototype.drawLineGraph = function (datapoints, group) { * @param JSONcontainer * @param svg */ -Linegraph.prototype.drawPoints = function (dataset, group, JSONcontainer, 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, group, JSONcontainer, 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 (group.options.drawPoints.style == 'circle') { - point = this._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 = 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"); + SVGutil.drawPoint(dataset[i].x, dataset[i].y, group, JSONcontainer, svg); } - 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. - 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.createElementNS('http://www.w3.org/2000/svg', elementType); - svgContainer.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.createElementNS('http://www.w3.org/2000/svg', elementType); - JSONcontainer[elementType] = {used: [], redundant: []}; - svgContainer.appendChild(element); - } - JSONcontainer[elementType].used.push(element); - return element; }; -/** - * 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 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]); - } - JSONcontainer[elementType].redundant = []; - } - } -}; -/** - * 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 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 @@ -4118,6 +4146,7 @@ Linegraph.prototype._prepareData = function (datapoints, options) { var xValue, yValue; var axis = this.yAxisLeft; var toScreen = this.body.util.toScreen; + this.zeroPosition = 0; if (options.yAxisOrientation == 'right') { axis = this.yAxisRight; @@ -4128,6 +4157,8 @@ Linegraph.prototype._prepareData = function (datapoints, options) { extractedData.push({x: xValue, y: yValue}); } + this.zeroPosition = Math.min(this.svg.offsetHeight, axis.convertValue(0)); + // extractedData.sort(function (a,b) {return a.x - b.x;}); return extractedData; }; diff --git a/examples/graph/02_random_nodes.html b/examples/graph/02_random_nodes.html index b435fd56..0c052e74 100755 --- a/examples/graph/02_random_nodes.html +++ b/examples/graph/02_random_nodes.html @@ -79,7 +79,7 @@ var options = { edges: { }, - stabilize: false, + stabilize: false }; graph = new vis.Graph(container, data, options); diff --git a/examples/graph2d/01_basic.html b/examples/graph2d/01_basic.html index b02bc807..f9ca41b6 100644 --- a/examples/graph2d/01_basic.html +++ b/examples/graph2d/01_basic.html @@ -51,8 +51,9 @@ content: names[1], className: "graphGroup1", options: { + style:'bar', yAxisOrientation: 'right', - drawPoints: true, // square, circle + drawPoints: false, // square, circle shaded: { orientation: 'top' // top, bottom } diff --git a/src/svgUtil.js b/src/svgUtil.js new file mode 100644 index 00000000..16d77f71 --- /dev/null +++ b/src/svgUtil.js @@ -0,0 +1,107 @@ +/** + * Created by Alex on 6/20/14. + */ + +var SVGutil = {} +/** + * this prepares the JSON container for allocating SVG elements + * @param JSONcontainer + * @private + */ +SVGutil._prepareSVGElements = function(JSONcontainer) { + // cleanup the redundant svgElements; + for (var elementType in JSONcontainer) { + if (JSONcontainer.hasOwnProperty(elementType)) { + JSONcontainer[elementType].redundant = JSONcontainer[elementType].used; + JSONcontainer[elementType].used = []; + } + } +}; + +/** + * 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 + */ +SVGutil._cleanupSVGElements = function(JSONcontainer) { + // cleanup the redundant svgElements; + 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]); + } + JSONcontainer[elementType].redundant = []; + } + } +}; + +/** + * 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 + */ +SVGutil._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 + // 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.createElementNS('http://www.w3.org/2000/svg', elementType); + svgContainer.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.createElementNS('http://www.w3.org/2000/svg', elementType); + JSONcontainer[elementType] = {used: [], redundant: []}; + svgContainer.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. + * 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 {*} + */ +SVGutil.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { + var point; + if (group.options.drawPoints.style == 'circle') { + point = SVGutil._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.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; +} \ No newline at end of file diff --git a/src/timeline/component/DataAxis.js b/src/timeline/component/DataAxis.js index 41624c93..6c8babc0 100644 --- a/src/timeline/component/DataAxis.js +++ b/src/timeline/component/DataAxis.js @@ -126,6 +126,8 @@ DataAxis.prototype._create = function() { }; DataAxis.prototype._redrawGroupIcons = function() { + SVGutil._prepareSVGElements(this.svgContainer); + var x; var iconWidth = this.options.iconWidth; var iconHeight = 15; @@ -144,6 +146,8 @@ DataAxis.prototype._redrawGroupIcons = function() { y += iconHeight + iconOffset; } } + + SVGutil._cleanupSVGElements(this.svgContainer); } /** @@ -389,7 +393,7 @@ DataAxis.prototype._redrawLabel = function (y, text, orientation, className, cha text += ''; - var largestWidth = this.props.majorCharWidth > this.props.minorCharWidth ? this.props.majorCharWidth : this.props.minorCharWidth; + var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth); if (this.maxLabelSize < text.length * largestWidth) { this.maxLabelSize = text.length * largestWidth; } @@ -504,5 +508,3 @@ DataAxis.prototype._calculateCharSize = function () { DataAxis.prototype.snap = function(date) { return this.step.snap(date); }; - - diff --git a/src/timeline/component/GraphGroup.js b/src/timeline/component/GraphGroup.js index 017fa494..9f88f985 100644 --- a/src/timeline/component/GraphGroup.js +++ b/src/timeline/component/GraphGroup.js @@ -4,14 +4,14 @@ * @param {Object} data * @param {ItemSet} itemSet */ -function GraphGroup (group, options, linegraph) { - var fields = ['yAxisOrientation','barGraph','drawPoints','catmullRom'] +function GraphGroup (group, options, groupsUsingDefaultStyles) { + var fields = ['style','yAxisOrientation','barChart','drawPoints','shaded','catmullRom'] this.options = util.selectiveDeepExtend(fields,{},options); - this.linegraph = linegraph; this.usingDefaultStyle = group.className === undefined; + this.groupsUsingDefaultStyles = groupsUsingDefaultStyles; this.update(group); if (this.usingDefaultStyle == true) { - this.linegraph.groupsUsingDefaultStyles += 1; + this.groupsUsingDefaultStyles[0] += 1; } } @@ -21,55 +21,57 @@ GraphGroup.prototype.setClass = function (className) { GraphGroup.prototype.setOptions = function(options) { if (options !== undefined) { - var fields = ['yAxisOrientation']; - util.selectiveExtend(fields, this.options, options); - this.linegraph._mergeOptions(this.options, options,'catmullRom'); - this.linegraph._mergeOptions(this.options, options,'drawPoints'); - this.linegraph._mergeOptions(this.options, options,'shaded'); + var fields = ['yAxisOrientation','style','barChart']; + util.selectiveDeepExtend(fields, this.options, options); + + util._mergeOptions(this.options, options,'catmullRom'); + util._mergeOptions(this.options, options,'drawPoints'); + util._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.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0]; this.setOptions(group.options); }; -GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, lineLength, iconHeight) { +GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, iconWidth, iconHeight) { var fillHeight = iconHeight * 0.5; var path, fillPath, outline; - if (this.options.barGraph.enabled == false) { - outline = this.linegraph._getSVGElement("rect", JSONcontainer, SVGcontainer); + 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", lineLength); + outline.setAttributeNS(null, "width", iconWidth); outline.setAttributeNS(null, "height", 2*fillHeight); outline.setAttributeNS(null, "class", "outline"); - path = this.linegraph._getSVGElement("path", JSONcontainer, SVGcontainer); + path = SVGutil._getSVGElement("path", JSONcontainer, SVGcontainer); path.setAttributeNS(null, "class", this.className); - path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + lineLength) + ","+y+""); + path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+""); if (this.options.shaded.enabled == true) { - fillPath = this.linegraph._getSVGElement("path", JSONcontainer, SVGcontainer); + fillPath = SVGutil._getSVGElement("path", JSONcontainer, SVGcontainer); 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)); + "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight)); } else { fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " + "L"+x+"," + (y + fillHeight) + " " + - "L"+ (x + lineLength) + "," + (y + fillHeight) + - "L"+ (x + lineLength) + ","+y); + "L"+ (x + iconWidth) + "," + (y + fillHeight) + + "L"+ (x + iconWidth) + ","+y); } fillPath.setAttributeNS(null, "class", this.className + " iconFill"); } if (this.options.drawPoints.enabled == true) { - this.linegraph.drawPoint(x + 0.5 * lineLength,y, this, JSONcontainer, SVGcontainer); + SVGutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer); } } else { + console.log("bar") //TODO: bars } } diff --git a/src/timeline/component/Legend.js b/src/timeline/component/Legend.js index 0ca76307..14c1a818 100644 --- a/src/timeline/component/Legend.js +++ b/src/timeline/component/Legend.js @@ -140,7 +140,7 @@ Legend.prototype.drawLegend = function() { fillPath.setAttributeNS(null, "class", classes[i] + " fill"); } - if (this.options.drawPoints.enabled == true) { + if (this.options._drawPoints.enabled == true) { this.drawPoint(x + 0.5 * lineLength,y,classes[i], this.svgLegendElements, this.svgLegend); } y += spacing; diff --git a/src/timeline/component/Linegraph.js b/src/timeline/component/Linegraph.js index 94a1c581..efa84355 100644 --- a/src/timeline/component/Linegraph.js +++ b/src/timeline/component/Linegraph.js @@ -10,9 +10,9 @@ function Linegraph(body, options) { enabled: true, orientation: 'top' // top, bottom }, - barGraph: { - enabled: false, - binSize: 'auto' + style: 'line', // line, bar + barChart: { + width: 50 }, drawPoints: { enabled: true, @@ -36,9 +36,18 @@ function Linegraph(body, options) { visible:true }, legend: { - orientation: 'left', // left, right - position: 'left', // left, center, right - visible: true + enabled: true, + axisIcons: true, + left: { + visible: true, + position: 'top-left', // top/bottom - left,center,right + textAlign: 'left' + }, + right: { + visible: true, + position: 'top-left', // top/bottom - left,center,right + textAlign: 'right' + } } }; @@ -86,8 +95,7 @@ function Linegraph(body, options) { this.svgElements = {}; this.setOptions(options); - this.groupsUsingDefaultStyles = 0; - + this.groupsUsingDefaultStyles = [0]; var me = this; this.body.emitter.on("rangechange",function() { @@ -104,7 +112,7 @@ function Linegraph(body, options) { this.body.emitter.on("rangechanged", function() { me.lastStart = me.body.range.start; me.svg.style.left = util.option.asSize(-me.width); - me.updateGraph.apply(me); + me._updateGraph.apply(me); }); // create the HTML DOM @@ -153,7 +161,7 @@ Linegraph.prototype._create = function(){ */ Linegraph.prototype.setOptions = function(options) { if (options) { - var fields = ['yAxisOrientation','dataAxis','legend']; + var fields = ['yAxisOrientation','style','barChart','dataAxis','legend']; util.selectiveDeepExtend(fields, this.options, options); if (options.catmullRom) { @@ -173,38 +181,12 @@ Linegraph.prototype.setOptions = function(options) { } } - console.log("OPTIONS:",this.options , options); - this._mergeOptions(this.options, options,'catmullRom'); - this._mergeOptions(this.options, options,'drawPoints'); - this._mergeOptions(this.options, options,'shaded'); + util._mergeOptions(this.options, options,'catmullRom'); + util._mergeOptions(this.options, options,'drawPoints'); + util._mergeOptions(this.options, options,'shaded'); } }; -/** - * 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') { - mergeTarget[option].enabled = options[option]; - } - else { - mergeTarget[option].enabled = true; - for (prop in options[option]) { - if (options[option].hasOwnProperty(prop)) { - mergeTarget[option][prop] = options[option][prop]; - } - } - } - } -} - /** * Hide the component from the DOM */ @@ -270,7 +252,7 @@ Linegraph.prototype.setItems = function(items) { this._onAdd(ids); } this._updateUngrouped(); - this.updateGraph(); + this._updateGraph(); this.redraw(); }; @@ -317,7 +299,7 @@ Linegraph.prototype.setGroups = function(groups) { this._onAddGroups(ids); } this._updateUngrouped(); - this.updateGraph(); + this._updateGraph(); this.redraw(); }; @@ -325,7 +307,7 @@ Linegraph.prototype.setGroups = function(groups) { Linegraph.prototype._onUpdate = function(ids) { this._updateUngrouped(); - this.updateGraph(); + this._updateGraph(); this.redraw(); }; Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate; @@ -334,8 +316,9 @@ 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.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]]); } @@ -354,7 +337,8 @@ Linegraph.prototype._onUpdateGroups = function (groupIds) { } } } - this.updateGraph(); + this._updateUngrouped(); + this._updateGraph(); this.redraw(); }; Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdateGroups; @@ -363,7 +347,8 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) { for (var i = 0; i < groupIds.length; i++) { this.legend.removeGroup(groupIds[i]); } - this.updateGraph(); + this._updateUngrouped(); + this._updateGraph(); this.redraw(); }; @@ -373,10 +358,11 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) { * @protected */ Linegraph.prototype._updateUngrouped = function() { - var group = {content: "graph"}; + var group = {id: UNGROUPED, content: "graph"}; if (!this.groups.hasOwnProperty(UNGROUPED)) { - this.groups[UNGROUPED] = new GraphGroup(group, this.options, this); + 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]); } @@ -447,7 +433,7 @@ Linegraph.prototype.redraw = function() { this.svg.style.left = util.option.asSize(-this.width); } if (zoomed == true) { - this.updateGraph(); + this._updateGraph(); } return resized; }; @@ -456,9 +442,9 @@ Linegraph.prototype.redraw = function() { * Update and redraw the graph. * */ -Linegraph.prototype.updateGraph = function () { +Linegraph.prototype._updateGraph = function () { // reset the svg elements - this._prepareSVGElements(this.svgElements); + SVGutil._prepareSVGElements(this.svgElements); if (this.width != 0 && this.itemsData != null) { // look at different lines @@ -466,15 +452,15 @@ Linegraph.prototype.updateGraph = function () { if (groupIds.length > 0) { this._updateYAxis(groupIds); for (var i = 0; i < groupIds.length; i++) { - this.drawGraph(groupIds[i], i, groupIds.length); + this._drawGraph(groupIds[i], i, groupIds.length); } } } -// this.legend.redraw(); + // this.legend.redraw(); // cleanup unused svg elements - this._cleanupSVGElements(this.svgElements); + SVGutil._cleanupSVGElements(this.svgElements); }; /** @@ -586,17 +572,17 @@ Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) { * @param groupIndex * @param amountOfGraphs */ -Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) { +Linegraph.prototype._drawGraph = function (groupId, groupIndex, amountOfGraphs) { var datapoints = this.itemsData.get({filter: function (item) {return item.group == groupId;}, type: {x:"Date"}}); // can be optimized, only has to be done once. var group = this.groups[groupId]; - if (this.options.barGraph.enabled == 'true') { - this.drawBarGraph(datapoints, group, amountOfGraphs); + if (group.options.style == 'line') { + this._drawLineGraph(datapoints, group); } else { - this.drawLineGraph(datapoints, group); + this._drawBarGraph(datapoints, group, amountOfGraphs); } }; @@ -606,13 +592,19 @@ Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) { * @param options * @param amountOfGraphs */ -Linegraph.prototype.drawBarGraph = function (datapoints, group, amountOfGraphs) { +Linegraph.prototype._drawBarGraph = function (datapoints, group, amountOfGraphs) { if (datapoints != null) { if (datapoints.length > 0) { - var dataset = this._prepareData(datapoints); - // draw points + var dataset = this._prepareData(datapoints, group.options); + var bar; + console.log(group.options); for (var i = 0; i < dataset.length; i++) { - this.drawBar(dataset[i].x, dataset[i].y, className); + this._drawBar(dataset[i].x, dataset[i].y, group.options.barChart.width, this.zeroPosition - dataset[i].y, group.className + ' bar'); + } + + // draw points + if (group.options.drawPoints.enabled == true) { + this._drawPoints(dataset, group, this.svgElements, this.svg); } } } @@ -625,15 +617,14 @@ Linegraph.prototype.drawBarGraph = function (datapoints, group, amountOfGraphs) * @param y * @param className */ -Linegraph.prototype.drawBar = function (x, y, className) { - var width = 10; - rect = this._getSVGElement('rect',this.svgElements, this.svg); +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", this.svg.offsetHeight - y); - rect.setAttributeNS(null, "class", className + " point"); + rect.setAttributeNS(null, "height", height); + rect.setAttributeNS(null, "class", className); }; @@ -643,13 +634,13 @@ Linegraph.prototype.drawBar = function (x, y, className) { * @param datapoints * @param options */ -Linegraph.prototype.drawLineGraph = function (datapoints, group) { +Linegraph.prototype._drawLineGraph = function (datapoints, group) { if (datapoints != null) { if (datapoints.length > 0) { var dataset = this._prepareData(datapoints, group.options); var path, d; - path = this._getSVGElement('path', this.svgElements, this.svg); + path = SVGutil._getSVGElement('path', this.svgElements, this.svg); path.setAttributeNS(null, "class", group.className); @@ -663,7 +654,7 @@ Linegraph.prototype.drawLineGraph = function (datapoints, group) { // append with points for fill and finalize the path if (group.options.shaded.enabled == true) { - var fillPath = this._getSVGElement('path',this.svgElements, this.svg); + var fillPath = SVGutil._getSVGElement('path',this.svgElements, this.svg); if (group.options.shaded.orientation == 'top') { var dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0; } @@ -678,7 +669,7 @@ Linegraph.prototype.drawLineGraph = function (datapoints, group) { // draw points if (group.options.drawPoints.enabled == true) { - this.drawPoints(dataset, group, this.svgElements, this.svg); + this._drawPoints(dataset, group, this.svgElements, this.svg); } } } @@ -692,112 +683,13 @@ Linegraph.prototype.drawLineGraph = function (datapoints, group) { * @param JSONcontainer * @param svg */ -Linegraph.prototype.drawPoints = function (dataset, group, JSONcontainer, 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, group, JSONcontainer, svg); + SVGutil.drawPoint(dataset[i].x, dataset[i].y, group, JSONcontainer, 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 (group.options.drawPoints.style == 'circle') { - point = this._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 = 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. - 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.createElementNS('http://www.w3.org/2000/svg', elementType); - svgContainer.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.createElementNS('http://www.w3.org/2000/svg', elementType); - JSONcontainer[elementType] = {used: [], redundant: []}; - svgContainer.appendChild(element); - } - JSONcontainer[elementType].used.push(element); - return element; -}; - -/** - * 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 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]); - } - JSONcontainer[elementType].redundant = []; - } - } -}; -/** - * 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 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 @@ -813,6 +705,7 @@ Linegraph.prototype._prepareData = function (datapoints, options) { var xValue, yValue; var axis = this.yAxisLeft; var toScreen = this.body.util.toScreen; + this.zeroPosition = 0; if (options.yAxisOrientation == 'right') { axis = this.yAxisRight; @@ -823,6 +716,8 @@ Linegraph.prototype._prepareData = function (datapoints, options) { extractedData.push({x: xValue, y: yValue}); } + this.zeroPosition = Math.min(this.svg.offsetHeight, axis.convertValue(0)); + // extractedData.sort(function (a,b) {return a.x - b.x;}); return extractedData; }; diff --git a/src/timeline/component/css/pathStyles.css b/src/timeline/component/css/pathStyles.css index 95665ef1..77c6d609 100644 --- a/src/timeline/component/css/pathStyles.css +++ b/src/timeline/component/css/pathStyles.css @@ -73,6 +73,11 @@ stroke: none; } + +.vis.timeline .bar { + fill-opacity:0.7; +} + .vis.timeline .point { stroke-width:2px; fill-opacity:1.0; diff --git a/src/util.js b/src/util.js index 0334939d..e1d7c324 100644 --- a/src/util.js +++ b/src/util.js @@ -1030,3 +1030,29 @@ util.copyObject = function(objectFrom, objectTo) { } return objectTo; }; + + +/** + * 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 + */ +util._mergeOptions = function (mergeTarget, options, option) { + if (options[option]) { + if (typeof options[option] == 'boolean') { + mergeTarget[option].enabled = options[option]; + } + else { + mergeTarget[option].enabled = true; + for (prop in options[option]) { + if (options[option].hasOwnProperty(prop)) { + mergeTarget[option][prop] = options[option][prop]; + } + } + } + } +} \ No newline at end of file