Browse Source

bargraphs!! woohoo!!

css_transitions
Alex de Mulder 10 years ago
parent
commit
b6e5a9dadb
12 changed files with 471 additions and 396 deletions
  1. +1
    -0
      Jakefile.js
  2. +5
    -0
      dist/vis.css
  3. +229
    -198
      dist/vis.js
  4. +1
    -1
      examples/graph/02_random_nodes.html
  5. +2
    -1
      examples/graph2d/01_basic.html
  6. +107
    -0
      src/svgUtil.js
  7. +5
    -3
      src/timeline/component/DataAxis.js
  8. +23
    -21
      src/timeline/component/GraphGroup.js
  9. +1
    -1
      src/timeline/component/Legend.js
  10. +66
    -171
      src/timeline/component/Linegraph.js
  11. +5
    -0
      src/timeline/component/css/pathStyles.css
  12. +26
    -0
      src/util.js

+ 1
- 0
Jakefile.js View File

@ -64,6 +64,7 @@ task('build', {async: true}, function () {
'./src/shim.js', './src/shim.js',
'./src/util.js', './src/util.js',
'./src/SVGutil.js',
'./src/DataSet.js', './src/DataSet.js',
'./src/DataView.js', './src/DataView.js',

+ 5
- 0
dist/vis.css View File

@ -447,6 +447,11 @@
stroke: none; stroke: none;
} }
.vis.timeline .bar {
fill-opacity:0.7;
}
.vis.timeline .point { .vis.timeline .point {
stroke-width:2px; stroke-width:2px;
fill-opacity:1.0; fill-opacity:1.0;

+ 229
- 198
dist/vis.js View File

@ -4,8 +4,8 @@
* *
* A dynamic, browser-based visualization library. * A dynamic, browser-based visualization library.
* *
* @version 1.1.0
* @date 2014-06-20
* @version @@version
* @date @@date
* *
* @license * @license
* Copyright (C) 2011-2014 Almende B.V, http://almende.com * Copyright (C) 2011-2014 Almende B.V, http://almende.com
@ -1341,6 +1341,138 @@ util.copyObject = function(objectFrom, objectTo) {
return 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 * DataSet
* *
@ -2568,14 +2700,14 @@ DataView.prototype.unsubscribe = DataView.prototype.off;
* @param {Object} data * @param {Object} data
* @param {ItemSet} itemSet * @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.options = util.selectiveDeepExtend(fields,{},options);
this.linegraph = linegraph;
this.usingDefaultStyle = group.className === undefined; this.usingDefaultStyle = group.className === undefined;
this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
this.update(group); this.update(group);
if (this.usingDefaultStyle == true) { 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) { GraphGroup.prototype.setOptions = function(options) {
if (options !== undefined) { 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) { GraphGroup.prototype.update = function(group) {
this.group = group; this.group = group;
this.content = group.content || 'graph'; 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); 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 fillHeight = iconHeight * 0.5;
var path, fillPath, outline; 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, "x", x);
outline.setAttributeNS(null, "y", y - fillHeight); 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, "height", 2*fillHeight);
outline.setAttributeNS(null, "class", "outline"); 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, "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) { 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') { if (this.options.shaded.orientation == 'top') {
fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) + 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 { else {
fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " + fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
"L"+x+"," + (y + fillHeight) + " " + "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"); fillPath.setAttributeNS(null, "class", this.className + " iconFill");
} }
if (this.options.drawPoints.enabled == true) { 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 { else {
console.log("bar")
//TODO: bars //TODO: bars
} }
} }
@ -2780,7 +2914,7 @@ Legend.prototype.drawLegend = function() {
fillPath.setAttributeNS(null, "class", classes[i] + " fill"); 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); this.drawPoint(x + 0.5 * lineLength,y,classes[i], this.svgLegendElements, this.svgLegend);
} }
y += spacing; y += spacing;
@ -2922,6 +3056,8 @@ DataAxis.prototype._create = function() {
}; };
DataAxis.prototype._redrawGroupIcons = function() { DataAxis.prototype._redrawGroupIcons = function() {
SVGutil._prepareSVGElements(this.svgContainer);
var x; var x;
var iconWidth = this.options.iconWidth; var iconWidth = this.options.iconWidth;
var iconHeight = 15; var iconHeight = 15;
@ -2940,6 +3076,8 @@ DataAxis.prototype._redrawGroupIcons = function() {
y += iconHeight + iconOffset; y += iconHeight + iconOffset;
} }
} }
SVGutil._cleanupSVGElements(this.svgContainer);
} }
/** /**
@ -3185,7 +3323,7 @@ DataAxis.prototype._redrawLabel = function (y, text, orientation, className, cha
text += ''; 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) { if (this.maxLabelSize < text.length * largestWidth) {
this.maxLabelSize = text.length * largestWidth; this.maxLabelSize = text.length * largestWidth;
} }
@ -3301,8 +3439,6 @@ DataAxis.prototype.snap = function(date) {
return this.step.snap(date); return this.step.snap(date);
}; };
var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
function Linegraph(body, options) { function Linegraph(body, options) {
@ -3315,9 +3451,9 @@ function Linegraph(body, options) {
enabled: true, enabled: true,
orientation: 'top' // top, bottom orientation: 'top' // top, bottom
}, },
barGraph: {
enabled: false,
binSize: 'auto'
style: 'line', // line, bar
barChart: {
width: 50
}, },
drawPoints: { drawPoints: {
enabled: true, enabled: true,
@ -3341,9 +3477,18 @@ function Linegraph(body, options) {
visible:true visible:true
}, },
legend: { 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.svgElements = {};
this.setOptions(options); this.setOptions(options);
this.groupsUsingDefaultStyles = 0;
this.groupsUsingDefaultStyles = [0];
var me = this; var me = this;
this.body.emitter.on("rangechange",function() { this.body.emitter.on("rangechange",function() {
@ -3409,7 +3553,7 @@ function Linegraph(body, options) {
this.body.emitter.on("rangechanged", function() { this.body.emitter.on("rangechanged", function() {
me.lastStart = me.body.range.start; me.lastStart = me.body.range.start;
me.svg.style.left = util.option.asSize(-me.width); me.svg.style.left = util.option.asSize(-me.width);
me.updateGraph.apply(me);
me._updateGraph.apply(me);
}); });
// create the HTML DOM // create the HTML DOM
@ -3458,7 +3602,7 @@ Linegraph.prototype._create = function(){
*/ */
Linegraph.prototype.setOptions = function(options) { Linegraph.prototype.setOptions = function(options) {
if (options) { if (options) {
var fields = ['yAxisOrientation','dataAxis','legend'];
var fields = ['yAxisOrientation','style','barChart','dataAxis','legend'];
util.selectiveDeepExtend(fields, this.options, options); util.selectiveDeepExtend(fields, this.options, options);
if (options.catmullRom) { 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 * Hide the component from the DOM
*/ */
@ -3575,7 +3693,7 @@ Linegraph.prototype.setItems = function(items) {
this._onAdd(ids); this._onAdd(ids);
} }
this._updateUngrouped(); this._updateUngrouped();
this.updateGraph();
this._updateGraph();
this.redraw(); this.redraw();
}; };
@ -3622,7 +3740,7 @@ Linegraph.prototype.setGroups = function(groups) {
this._onAddGroups(ids); this._onAddGroups(ids);
} }
this._updateUngrouped(); this._updateUngrouped();
this.updateGraph();
this._updateGraph();
this.redraw(); this.redraw();
}; };
@ -3630,7 +3748,7 @@ Linegraph.prototype.setGroups = function(groups) {
Linegraph.prototype._onUpdate = function(ids) { Linegraph.prototype._onUpdate = function(ids) {
this._updateUngrouped(); this._updateUngrouped();
this.updateGraph();
this._updateGraph();
this.redraw(); this.redraw();
}; };
Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate; Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate;
@ -3639,8 +3757,9 @@ Linegraph.prototype._onUpdateGroups = function (groupIds) {
for (var i = 0; i < groupIds.length; i++) { for (var i = 0; i < groupIds.length; i++) {
var group = this.groupsData.get(groupIds[i]); var group = this.groupsData.get(groupIds[i]);
if (!this.groups.hasOwnProperty(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]]); this.legend.addGroup(groupIds[i],this.groups[groupIds[i]]);
if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
this.yAxisRight.addGroup(groupIds[i], this.groups[groupIds[i]]); 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(); this.redraw();
}; };
Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdateGroups; Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdateGroups;
@ -3668,7 +3788,8 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) {
for (var i = 0; i < groupIds.length; i++) { for (var i = 0; i < groupIds.length; i++) {
this.legend.removeGroup(groupIds[i]); this.legend.removeGroup(groupIds[i]);
} }
this.updateGraph();
this._updateUngrouped();
this._updateGraph();
this.redraw(); this.redraw();
}; };
@ -3678,10 +3799,11 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) {
* @protected * @protected
*/ */
Linegraph.prototype._updateUngrouped = function() { Linegraph.prototype._updateUngrouped = function() {
var group = {content: "graph"};
var group = {id: UNGROUPED, content: "graph"};
if (!this.groups.hasOwnProperty(UNGROUPED)) { 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]); this.legend.addGroup(UNGROUPED,this.groups[UNGROUPED]);
if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') {
this.yAxisRight.addGroup(UNGROUPED, this.groups[UNGROUPED]); this.yAxisRight.addGroup(UNGROUPED, this.groups[UNGROUPED]);
} }
@ -3752,7 +3874,7 @@ Linegraph.prototype.redraw = function() {
this.svg.style.left = util.option.asSize(-this.width); this.svg.style.left = util.option.asSize(-this.width);
} }
if (zoomed == true) { if (zoomed == true) {
this.updateGraph();
this._updateGraph();
} }
return resized; return resized;
}; };
@ -3761,9 +3883,9 @@ Linegraph.prototype.redraw = function() {
* Update and redraw the graph. * Update and redraw the graph.
* *
*/ */
Linegraph.prototype.updateGraph = function () {
Linegraph.prototype._updateGraph = function () {
// reset the svg elements // reset the svg elements
this._prepareSVGElements(this.svgElements);
SVGutil._prepareSVGElements(this.svgElements);
if (this.width != 0 && this.itemsData != null) { if (this.width != 0 && this.itemsData != null) {
// look at different lines // look at different lines
@ -3771,15 +3893,15 @@ Linegraph.prototype.updateGraph = function () {
if (groupIds.length > 0) { if (groupIds.length > 0) {
this._updateYAxis(groupIds); this._updateYAxis(groupIds);
for (var i = 0; i < groupIds.length; i++) { 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 // 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 groupIndex
* @param amountOfGraphs * @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"}}); var datapoints = this.itemsData.get({filter: function (item) {return item.group == groupId;}, type: {x:"Date"}});
// can be optimized, only has to be done once. // can be optimized, only has to be done once.
var group = this.groups[groupId]; 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 { else {
this.drawLineGraph(datapoints, group);
this._drawBarGraph(datapoints, group, amountOfGraphs);
} }
}; };
@ -3911,13 +4033,19 @@ Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) {
* @param options * @param options
* @param amountOfGraphs * @param amountOfGraphs
*/ */
Linegraph.prototype.drawBarGraph = function (datapoints, group, amountOfGraphs) {
Linegraph.prototype._drawBarGraph = function (datapoints, group, amountOfGraphs) {
if (datapoints != null) { if (datapoints != null) {
if (datapoints.length > 0) { 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++) { 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 y
* @param className * @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, "x", x - 0.5 * width);
rect.setAttributeNS(null, "y", y); rect.setAttributeNS(null, "y", y);
rect.setAttributeNS(null, "width", width); 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 datapoints
* @param options * @param options
*/ */
Linegraph.prototype.drawLineGraph = function (datapoints, group) {
Linegraph.prototype._drawLineGraph = function (datapoints, group) {
if (datapoints != null) { if (datapoints != null) {
if (datapoints.length > 0) { if (datapoints.length > 0) {
var dataset = this._prepareData(datapoints, group.options); var dataset = this._prepareData(datapoints, group.options);
var path, d; 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); 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 // append with points for fill and finalize the path
if (group.options.shaded.enabled == true) { 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') { if (group.options.shaded.orientation == 'top') {
var dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0; 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 // draw points
if (group.options.drawPoints.enabled == true) { 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 JSONcontainer
* @param svg * @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++) { 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 * 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 xValue, yValue;
var axis = this.yAxisLeft; var axis = this.yAxisLeft;
var toScreen = this.body.util.toScreen; var toScreen = this.body.util.toScreen;
this.zeroPosition = 0;
if (options.yAxisOrientation == 'right') { if (options.yAxisOrientation == 'right') {
axis = this.yAxisRight; axis = this.yAxisRight;
@ -4128,6 +4157,8 @@ Linegraph.prototype._prepareData = function (datapoints, options) {
extractedData.push({x: xValue, y: yValue}); 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;}); // extractedData.sort(function (a,b) {return a.x - b.x;});
return extractedData; return extractedData;
}; };

+ 1
- 1
examples/graph/02_random_nodes.html View File

@ -79,7 +79,7 @@
var options = { var options = {
edges: { edges: {
}, },
stabilize: false,
stabilize: false
}; };
graph = new vis.Graph(container, data, options); graph = new vis.Graph(container, data, options);

+ 2
- 1
examples/graph2d/01_basic.html View File

@ -51,8 +51,9 @@
content: names[1], content: names[1],
className: "graphGroup1", className: "graphGroup1",
options: { options: {
style:'bar',
yAxisOrientation: 'right', yAxisOrientation: 'right',
drawPoints: true, // square, circle
drawPoints: false, // square, circle
shaded: { shaded: {
orientation: 'top' // top, bottom orientation: 'top' // top, bottom
} }

+ 107
- 0
src/svgUtil.js View File

@ -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;
}

+ 5
- 3
src/timeline/component/DataAxis.js View File

@ -126,6 +126,8 @@ DataAxis.prototype._create = function() {
}; };
DataAxis.prototype._redrawGroupIcons = function() { DataAxis.prototype._redrawGroupIcons = function() {
SVGutil._prepareSVGElements(this.svgContainer);
var x; var x;
var iconWidth = this.options.iconWidth; var iconWidth = this.options.iconWidth;
var iconHeight = 15; var iconHeight = 15;
@ -144,6 +146,8 @@ DataAxis.prototype._redrawGroupIcons = function() {
y += iconHeight + iconOffset; y += iconHeight + iconOffset;
} }
} }
SVGutil._cleanupSVGElements(this.svgContainer);
} }
/** /**
@ -389,7 +393,7 @@ DataAxis.prototype._redrawLabel = function (y, text, orientation, className, cha
text += ''; 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) { if (this.maxLabelSize < text.length * largestWidth) {
this.maxLabelSize = text.length * largestWidth; this.maxLabelSize = text.length * largestWidth;
} }
@ -504,5 +508,3 @@ DataAxis.prototype._calculateCharSize = function () {
DataAxis.prototype.snap = function(date) { DataAxis.prototype.snap = function(date) {
return this.step.snap(date); return this.step.snap(date);
}; };

+ 23
- 21
src/timeline/component/GraphGroup.js View File

@ -4,14 +4,14 @@
* @param {Object} data * @param {Object} data
* @param {ItemSet} itemSet * @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.options = util.selectiveDeepExtend(fields,{},options);
this.linegraph = linegraph;
this.usingDefaultStyle = group.className === undefined; this.usingDefaultStyle = group.className === undefined;
this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
this.update(group); this.update(group);
if (this.usingDefaultStyle == true) { 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) { GraphGroup.prototype.setOptions = function(options) {
if (options !== undefined) { 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) { GraphGroup.prototype.update = function(group) {
this.group = group; this.group = group;
this.content = group.content || 'graph'; 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); 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 fillHeight = iconHeight * 0.5;
var path, fillPath, outline; 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, "x", x);
outline.setAttributeNS(null, "y", y - fillHeight); 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, "height", 2*fillHeight);
outline.setAttributeNS(null, "class", "outline"); 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, "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) { 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') { if (this.options.shaded.orientation == 'top') {
fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) + 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 { else {
fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " + fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
"L"+x+"," + (y + fillHeight) + " " + "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"); fillPath.setAttributeNS(null, "class", this.className + " iconFill");
} }
if (this.options.drawPoints.enabled == true) { 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 { else {
console.log("bar")
//TODO: bars //TODO: bars
} }
} }

+ 1
- 1
src/timeline/component/Legend.js View File

@ -140,7 +140,7 @@ Legend.prototype.drawLegend = function() {
fillPath.setAttributeNS(null, "class", classes[i] + " fill"); 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); this.drawPoint(x + 0.5 * lineLength,y,classes[i], this.svgLegendElements, this.svgLegend);
} }
y += spacing; y += spacing;

+ 66
- 171
src/timeline/component/Linegraph.js View File

@ -10,9 +10,9 @@ function Linegraph(body, options) {
enabled: true, enabled: true,
orientation: 'top' // top, bottom orientation: 'top' // top, bottom
}, },
barGraph: {
enabled: false,
binSize: 'auto'
style: 'line', // line, bar
barChart: {
width: 50
}, },
drawPoints: { drawPoints: {
enabled: true, enabled: true,
@ -36,9 +36,18 @@ function Linegraph(body, options) {
visible:true visible:true
}, },
legend: { 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.svgElements = {};
this.setOptions(options); this.setOptions(options);
this.groupsUsingDefaultStyles = 0;
this.groupsUsingDefaultStyles = [0];
var me = this; var me = this;
this.body.emitter.on("rangechange",function() { this.body.emitter.on("rangechange",function() {
@ -104,7 +112,7 @@ function Linegraph(body, options) {
this.body.emitter.on("rangechanged", function() { this.body.emitter.on("rangechanged", function() {
me.lastStart = me.body.range.start; me.lastStart = me.body.range.start;
me.svg.style.left = util.option.asSize(-me.width); me.svg.style.left = util.option.asSize(-me.width);
me.updateGraph.apply(me);
me._updateGraph.apply(me);
}); });
// create the HTML DOM // create the HTML DOM
@ -153,7 +161,7 @@ Linegraph.prototype._create = function(){
*/ */
Linegraph.prototype.setOptions = function(options) { Linegraph.prototype.setOptions = function(options) {
if (options) { if (options) {
var fields = ['yAxisOrientation','dataAxis','legend'];
var fields = ['yAxisOrientation','style','barChart','dataAxis','legend'];
util.selectiveDeepExtend(fields, this.options, options); util.selectiveDeepExtend(fields, this.options, options);
if (options.catmullRom) { 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 * Hide the component from the DOM
*/ */
@ -270,7 +252,7 @@ Linegraph.prototype.setItems = function(items) {
this._onAdd(ids); this._onAdd(ids);
} }
this._updateUngrouped(); this._updateUngrouped();
this.updateGraph();
this._updateGraph();
this.redraw(); this.redraw();
}; };
@ -317,7 +299,7 @@ Linegraph.prototype.setGroups = function(groups) {
this._onAddGroups(ids); this._onAddGroups(ids);
} }
this._updateUngrouped(); this._updateUngrouped();
this.updateGraph();
this._updateGraph();
this.redraw(); this.redraw();
}; };
@ -325,7 +307,7 @@ Linegraph.prototype.setGroups = function(groups) {
Linegraph.prototype._onUpdate = function(ids) { Linegraph.prototype._onUpdate = function(ids) {
this._updateUngrouped(); this._updateUngrouped();
this.updateGraph();
this._updateGraph();
this.redraw(); this.redraw();
}; };
Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate; Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate;
@ -334,8 +316,9 @@ Linegraph.prototype._onUpdateGroups = function (groupIds) {
for (var i = 0; i < groupIds.length; i++) { for (var i = 0; i < groupIds.length; i++) {
var group = this.groupsData.get(groupIds[i]); var group = this.groupsData.get(groupIds[i]);
if (!this.groups.hasOwnProperty(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]]); this.legend.addGroup(groupIds[i],this.groups[groupIds[i]]);
if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
this.yAxisRight.addGroup(groupIds[i], this.groups[groupIds[i]]); 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(); this.redraw();
}; };
Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdateGroups; Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdateGroups;
@ -363,7 +347,8 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) {
for (var i = 0; i < groupIds.length; i++) { for (var i = 0; i < groupIds.length; i++) {
this.legend.removeGroup(groupIds[i]); this.legend.removeGroup(groupIds[i]);
} }
this.updateGraph();
this._updateUngrouped();
this._updateGraph();
this.redraw(); this.redraw();
}; };
@ -373,10 +358,11 @@ Linegraph.prototype._onRemoveGroups = function (groupIds) {
* @protected * @protected
*/ */
Linegraph.prototype._updateUngrouped = function() { Linegraph.prototype._updateUngrouped = function() {
var group = {content: "graph"};
var group = {id: UNGROUPED, content: "graph"};
if (!this.groups.hasOwnProperty(UNGROUPED)) { 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]); this.legend.addGroup(UNGROUPED,this.groups[UNGROUPED]);
if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') {
this.yAxisRight.addGroup(UNGROUPED, this.groups[UNGROUPED]); this.yAxisRight.addGroup(UNGROUPED, this.groups[UNGROUPED]);
} }
@ -447,7 +433,7 @@ Linegraph.prototype.redraw = function() {
this.svg.style.left = util.option.asSize(-this.width); this.svg.style.left = util.option.asSize(-this.width);
} }
if (zoomed == true) { if (zoomed == true) {
this.updateGraph();
this._updateGraph();
} }
return resized; return resized;
}; };
@ -456,9 +442,9 @@ Linegraph.prototype.redraw = function() {
* Update and redraw the graph. * Update and redraw the graph.
* *
*/ */
Linegraph.prototype.updateGraph = function () {
Linegraph.prototype._updateGraph = function () {
// reset the svg elements // reset the svg elements
this._prepareSVGElements(this.svgElements);
SVGutil._prepareSVGElements(this.svgElements);
if (this.width != 0 && this.itemsData != null) { if (this.width != 0 && this.itemsData != null) {
// look at different lines // look at different lines
@ -466,15 +452,15 @@ Linegraph.prototype.updateGraph = function () {
if (groupIds.length > 0) { if (groupIds.length > 0) {
this._updateYAxis(groupIds); this._updateYAxis(groupIds);
for (var i = 0; i < groupIds.length; i++) { 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 // 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 groupIndex
* @param amountOfGraphs * @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"}}); var datapoints = this.itemsData.get({filter: function (item) {return item.group == groupId;}, type: {x:"Date"}});
// can be optimized, only has to be done once. // can be optimized, only has to be done once.
var group = this.groups[groupId]; 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 { else {
this.drawLineGraph(datapoints, group);
this._drawBarGraph(datapoints, group, amountOfGraphs);
} }
}; };
@ -606,13 +592,19 @@ Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) {
* @param options * @param options
* @param amountOfGraphs * @param amountOfGraphs
*/ */
Linegraph.prototype.drawBarGraph = function (datapoints, group, amountOfGraphs) {
Linegraph.prototype._drawBarGraph = function (datapoints, group, amountOfGraphs) {
if (datapoints != null) { if (datapoints != null) {
if (datapoints.length > 0) { 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++) { 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 y
* @param className * @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, "x", x - 0.5 * width);
rect.setAttributeNS(null, "y", y); rect.setAttributeNS(null, "y", y);
rect.setAttributeNS(null, "width", width); 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 datapoints
* @param options * @param options
*/ */
Linegraph.prototype.drawLineGraph = function (datapoints, group) {
Linegraph.prototype._drawLineGraph = function (datapoints, group) {
if (datapoints != null) { if (datapoints != null) {
if (datapoints.length > 0) { if (datapoints.length > 0) {
var dataset = this._prepareData(datapoints, group.options); var dataset = this._prepareData(datapoints, group.options);
var path, d; 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); 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 // append with points for fill and finalize the path
if (group.options.shaded.enabled == true) { 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') { if (group.options.shaded.orientation == 'top') {
var dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0; 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 // draw points
if (group.options.drawPoints.enabled == true) { 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 JSONcontainer
* @param svg * @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++) { 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 * 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 xValue, yValue;
var axis = this.yAxisLeft; var axis = this.yAxisLeft;
var toScreen = this.body.util.toScreen; var toScreen = this.body.util.toScreen;
this.zeroPosition = 0;
if (options.yAxisOrientation == 'right') { if (options.yAxisOrientation == 'right') {
axis = this.yAxisRight; axis = this.yAxisRight;
@ -823,6 +716,8 @@ Linegraph.prototype._prepareData = function (datapoints, options) {
extractedData.push({x: xValue, y: yValue}); 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;}); // extractedData.sort(function (a,b) {return a.x - b.x;});
return extractedData; return extractedData;
}; };

+ 5
- 0
src/timeline/component/css/pathStyles.css View File

@ -73,6 +73,11 @@
stroke: none; stroke: none;
} }
.vis.timeline .bar {
fill-opacity:0.7;
}
.vis.timeline .point { .vis.timeline .point {
stroke-width:2px; stroke-width:2px;
fill-opacity:1.0; fill-opacity:1.0;

+ 26
- 0
src/util.js View File

@ -1030,3 +1030,29 @@ util.copyObject = function(objectFrom, objectTo) {
} }
return 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];
}
}
}
}
}

Loading…
Cancel
Save