Browse Source

everything but legend and barchart working. two axis! woohoo!

css_transitions
Alex de Mulder 10 years ago
parent
commit
007a4a90b1
13 changed files with 904 additions and 368 deletions
  1. +1
    -0
      Jakefile.js
  2. +10
    -10
      dist/vis.css
  3. +403
    -163
      dist/vis.js
  4. +1
    -1
      dist/vis.min.css
  5. +12
    -12
      dist/vis.min.js
  6. +72
    -17
      examples/Graph2d/01_basic.html
  7. +14
    -4
      src/timeline/DataStep.js
  8. +22
    -0
      src/timeline/Graph2d.js
  9. +86
    -51
      src/timeline/component/DataAxis.js
  10. +269
    -100
      src/timeline/component/Linegraph.js
  11. +10
    -10
      src/timeline/component/css/pathStyles.css
  12. +3
    -0
      src/timeline/component/legend.js
  13. +1
    -0
      src/util.js

+ 1
- 0
Jakefile.js View File

@ -67,6 +67,7 @@ task('build', {async: true}, function () {
'./src/DataSet.js',
'./src/DataView.js',
'./src/timeline/component/Legend.js',
'./src/timeline/component/DataAxis.js',
'./src/timeline/component/Linegraph.js',
'./src/timeline/DataStep.js',

+ 10
- 10
dist/vis.css View File

@ -357,70 +357,70 @@
white-space: nowrap;
}
.vis.timeline .group0 {
.vis.timeline .graphGroup0 {
fill:#4f81bd;
fill-opacity:0;
stroke-width:2px;
stroke: #4f81bd;
}
.vis.timeline .group1 {
.vis.timeline .graphGroup1 {
fill:#f79646;
fill-opacity:0;
stroke-width:2px;
stroke: #f79646;
}
.vis.timeline .group2 {
.vis.timeline .graphGroup2 {
fill: #8c51cf;
fill-opacity:0;
stroke-width:2px;
stroke: #8c51cf;
}
.vis.timeline .group3 {
.vis.timeline .graphGroup3 {
fill: #75c841;
fill-opacity:0;
stroke-width:2px;
stroke: #75c841;
}
.vis.timeline .group4 {
.vis.timeline .graphGroup4 {
fill: #ff0100;
fill-opacity:0;
stroke-width:2px;
stroke: #ff0100;
}
.vis.timeline .group5 {
.vis.timeline .graphGroup5 {
fill: #37d8e6;
fill-opacity:0;
stroke-width:2px;
stroke: #37d8e6;
}
.vis.timeline .group6 {
.vis.timeline .graphGroup6 {
fill: #042662;
fill-opacity:0;
stroke-width:2px;
stroke: #042662;
}
.vis.timeline .group7 {
.vis.timeline .graphGroup7 {
fill:#00ff26;
fill-opacity:0;
stroke-width:2px;
stroke: #00ff26;
}
.vis.timeline .group8 {
.vis.timeline .graphGroup8 {
fill:#ff00ff;
fill-opacity:0;
stroke-width:2px;
stroke: #ff00ff;
}
.vis.timeline .group9 {
.vis.timeline .graphGroup9 {
fill: #8f3938;
fill-opacity:0;
stroke-width:2px;

+ 403
- 163
dist/vis.js View File

@ -1298,6 +1298,7 @@ util.copyObject = function(objectFrom, objectTo) {
}
}
}
return objectTo;
};
/**
@ -2557,6 +2558,10 @@ DataView.prototype._trigger = DataSet.prototype._trigger;
DataView.prototype.subscribe = DataView.prototype.on;
DataView.prototype.unsubscribe = DataView.prototype.off;
/**
* Created by Alex on 6/17/14.
*/
/**
* A horizontal time axis
* @param {Object} [options] See DataAxis.setOptions for the available
@ -2572,6 +2577,8 @@ function DataAxis (body, options) {
orientation: 'left', // supported: 'left'
showMinorLabels: true,
showMajorLabels: true,
majorLinesOffset: 25,
minorLinesOffset: 25,
width: '50px',
height: '300px'
};
@ -2606,15 +2613,32 @@ function DataAxis (body, options) {
this.width = Number(this.options.width.replace("px",""));
this.height = Number(this.options.height.replace("px",""));
this.stepPixels = 25;
this.stepPixelsForced = 25;
this.lineOffset = 0;
this.master = true;
// create the HTML DOM
this._create();
}
DataAxis.prototype = new Component();
//DataAxis.prototype.setOptions = function(options) {
//
//}
DataAxis.prototype.setOptions = function(options) {
if (options) {
var redraw = false;
if (this.options.orientation != options.orientation && options.orientation !== undefined) {
redraw = true;
}
var fields = ['orientation','showMinorLabels','showMajorLabels','width','height'];
util.selectiveExtend(fields, this.options, options);
if (redraw == true && this.dom.frame) {
this.hide();
this.show();
}
}
}
/**
@ -2628,8 +2652,6 @@ DataAxis.prototype._create = function() {
this.dom.lineContainer = document.createElement('div');
this.dom.lineContainer.style.width = '100%';
this.dom.lineContainer.style.height = this.options.height;
this.show();
};
@ -2641,7 +2663,7 @@ DataAxis.prototype.show = function() {
if (this.options.orientation == 'left') {
this.body.dom.left.appendChild(this.dom.frame);
}
if (this.options.orientation == 'right') {
else {
this.body.dom.right.appendChild(this.dom.frame);
}
}
@ -2698,9 +2720,9 @@ DataAxis.prototype.redraw = function () {
props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth;
props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2*this.options.minorLinesOffset;
props.minorLineHeight = 1;
props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth;
props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2*this.options.majorLinesOffset;;
props.majorLineHeight = 1;
// take frame offline while updating (is almost twice as fast)
@ -2751,13 +2773,27 @@ DataAxis.prototype._redrawLabels = function () {
step.first();
var stepPixels = this.dom.frame.offsetHeight / ((step.marginRange / step.step) + 1);
var xFirstMajorLabel = undefined;
this.stepPixels = stepPixels;
var amountOfSteps = this.height / stepPixels;
var stepDifference = 0;
if (this.master == false) {
stepPixels = this.stepPixelsForced;
stepDifference = Math.round((this.height / stepPixels) - amountOfSteps);
for (var i = 0; i < 0.5 * stepDifference; i++) {
step.previous();
}
amountOfSteps = this.height / stepPixels;
}
var xFirstMajorLabel = undefined;
this.valueAtZero = step.marginEnd;
var marginStartPos = 0;
var max = 0;
while (step.hasNext() && max < 1000) {
var y = Math.round(max * stepPixels);
var y = 0;
while (max < amountOfSteps) {
y = Math.round(max * stepPixels);
marginStartPos = max * stepPixels;
var isMajor = step.isMajor();
@ -2766,7 +2802,7 @@ DataAxis.prototype._redrawLabels = function () {
}
if (isMajor && this.options['showMajorLabels']) {
if (y > 0) {
if (y >= 0) {
if (xFirstMajorLabel == undefined) {
xFirstMajorLabel = y;
}
@ -2782,7 +2818,7 @@ DataAxis.prototype._redrawLabels = function () {
max++;
}
this.conversionFactor = marginStartPos/step.marginRange;
this.conversionFactor = marginStartPos/((amountOfSteps-1) * step.step);
// create a major label on the left when needed
if (this.options['showMajorLabels']) {
@ -2816,7 +2852,7 @@ DataAxis.prototype.convertValues = function(data) {
DataAxis.prototype.convertValue = function(value) {
var invertedValue = this.valueAtZero - value;
var convertedValue = invertedValue * this.conversionFactor;
return convertedValue - 2; // the -2 is to compensate for the borders
return convertedValue; // the -2 is to compensate for the borders
}
/**
@ -2884,7 +2920,7 @@ DataAxis.prototype._redrawMajorText = function (y, text, orientation) {
label.style.textAlign = "right";
}
else {
label.style.left = '2';
label.style.left = '2px';
label.style.textAlign = "left";
}
@ -2898,27 +2934,29 @@ DataAxis.prototype._redrawMajorText = function (y, text, orientation) {
* @private
*/
DataAxis.prototype._redrawMinorLine = function (y, orientation) {
// reuse redundant line
var line = this.dom.redundant.minorLines.shift();
if (this.master == true) {
// reuse redundant line
var line = this.dom.redundant.minorLines.shift();
if (!line) {
// create vertical line
line = document.createElement('div');
line.className = 'grid horizontal minor';
this.dom.lineContainer.appendChild(line);
}
this.dom.minorLines.push(line);
if (!line) {
// create vertical line
line = document.createElement('div');
line.className = 'grid horizontal minor';
this.dom.lineContainer.appendChild(line);
}
this.dom.minorLines.push(line);
var props = this.props;
if (orientation == 'left') {
line.style.left = (this.width - 15) + 'px';
}
else {
line.style.left = -1*(this.width - 15) + 'px';
}
var props = this.props;
if (orientation == 'left') {
line.style.left = (this.width - this.options.minorLinesOffset) + 'px';
}
else {
line.style.left = -1*(this.width - this.options.minorLinesOffset) + 'px';
}
line.style.width = props.minorLineWidth + 'px';
line.style.top = y + 'px';
line.style.width = props.minorLineWidth + 'px';
line.style.top = y + 'px';
}
};
/**
@ -2928,25 +2966,27 @@ DataAxis.prototype._redrawMinorLine = function (y, orientation) {
* @private
*/
DataAxis.prototype._redrawMajorLine = function (y, orientation) {
// reuse redundant line
var line = this.dom.redundant.majorLines.shift();
if (this.master == true) {
// reuse redundant line
var line = this.dom.redundant.majorLines.shift();
if (!line) {
// create vertical line
line = document.createElement('div');
line.className = 'grid horizontal major';
this.dom.lineContainer.appendChild(line);
}
this.dom.majorLines.push(line);
if (!line) {
// create vertical line
line = document.createElement('div');
line.className = 'grid horizontal major';
this.dom.lineContainer.appendChild(line);
}
this.dom.majorLines.push(line);
if (orientation == 'left') {
line.style.left = (this.width - 25) + 'px';
}
else {
line.style.left = -1*(this.width - 25) + 'px';
if (orientation == 'left') {
line.style.left = (this.width - this.options.majorLinesOffset) + 'px';
}
else {
line.style.left = -1*(this.width - this.options.mayorLinesOffset) + 'px';
}
line.style.top = y + 'px';
line.style.width = this.props.majorLineWidth + 'px';
}
line.style.top = y + 'px';
line.style.width = this.props.majorLineWidth + 'px';
};
@ -3001,6 +3041,7 @@ function Linegraph(body, options) {
this.body = body;
this.defaultOptions = {
yAxisOrientation: 'left',
shaded: {
enabled: true,
orientation: 'top' // top, bottom
@ -3044,6 +3085,19 @@ function Linegraph(body, options) {
}
};
// listeners for the DataSet of the groups
this.groupListeners = {
'add': function (event, params, senderId) {
me._onAddGroups(params.items);
},
'update': function (event, params, senderId) {
me._onUpdateGroups(params.items);
},
'remove': function (event, params, senderId) {
me._onRemoveGroups(params.items);
}
};
this.items = {}; // object with an Item for every data item
this.selection = []; // list with the ids of all selected nodes
this.lastStart = this.body.range.start;
@ -3051,11 +3105,10 @@ function Linegraph(body, options) {
this.svgElements = {};
this.svgLegendElements = {};
// create the HTML DOM
this.setOptions(options);
this._create();
var me = this;
var me = this;
this.body.emitter.on("rangechange",function() {
if (me.lastStart != 0) {
var offset = me.body.range.start - me.lastStart;
@ -3073,7 +3126,8 @@ function Linegraph(body, options) {
me.updateGraph.apply(me);
});
this.setOptions(options);
// create the HTML DOM
this._create();
this.body.emitter.emit("change");
}
@ -3105,10 +3159,22 @@ Linegraph.prototype._create = function(){
frame.appendChild(this.svgLegend);
// panel with time axis
this.yAxis = new DataAxis(this.body, {
this.yAxisLeft = new DataAxis(this.body, {
orientation: 'left',
showMinorLabels: true,
showMajorLabels: true,
majorLinesOffset: 25,
minorLinesOffset: 25,
width: '50px',
height: this.svg.style.height
});
this.yAxisRight = new DataAxis(this.body, {
orientation: 'right',
showMinorLabels: true,
showMajorLabels: true,
majorLinesOffset: 25,
minorLinesOffset: 25,
width: '50px',
height: this.svg.style.height
});
@ -3116,22 +3182,13 @@ Linegraph.prototype._create = function(){
this.show();
};
Linegraph.prototype.setOptions = function(options) {
if (options) {
var fields = ['barGraph','catmullRom','shaded','drawPoints'];
var fields = ['yAxisOrientation'];
util.selectiveExtend(fields, this.options, options);
if (options.catmullRom) {
if (typeof options.catmullRom == 'boolean') {
this.options.catmullRom.enabled = options.catmullRom;
}
else {
this.options.catmullRom.enabled = true;
for (var prop in options.catmullRom) {
if (options.catmullRom.hasOwnProperty(prop)) {
this.options.catmullRom[prop] = options.catmullRom[prop];
}
}
if (typeof options.catmullRom == 'object') {
if (options.catmullRom.parametrization) {
if (options.catmullRom.parametrization == 'uniform') {
this.options.catmullRom.alpha = 0;
@ -3146,36 +3203,27 @@ Linegraph.prototype.setOptions = function(options) {
}
}
}
this._mergeOptions(this.options, options,'catmullRom');
this._mergeOptions(this.options, options,'drawPoints');
this._mergeOptions(this.options, options,'shaded');
}
};
if (options.drawPoints) {
if (typeof options.catmullRom == 'boolean') {
this.options.drawPoints.enabled = options.drawPoints;
}
else {
this.options.drawPoints.enabled = true;
for (prop in options.drawPoints) {
if (options.drawPoints.hasOwnProperty(prop)) {
this.options.drawPoints[prop] = options.drawPoints[prop];
}
}
}
Linegraph.prototype._mergeOptions = function (mergeTarget, options,option) {
if (options[option]) {
if (typeof options[option] == 'boolean') {
mergeTarget[option].enabled = options[option];
}
if (options.shaded) {
if (typeof options.shaded == 'boolean') {
this.options.shaded.enabled = options.shaded;
}
else {
this.options.shaded.enabled = true;
for (prop in options.shaded) {
if (options.shaded.hasOwnProperty(prop)) {
this.options.shaded[prop] = options.shaded.drawPoints[prop];
}
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
@ -3199,8 +3247,6 @@ Linegraph.prototype.show = function() {
};
/**
* Set items
* @param {vis.DataSet | null} items
@ -3243,31 +3289,91 @@ Linegraph.prototype.setItems = function(items) {
ids = this.itemsData.getIds();
this._onAdd(ids);
}
this._updateUngrouped();
this.updateGraph();
this.redraw();
};
/**
* Handle added items
* @param {Number[]} ids
* @protected
* Set groups
* @param {vis.DataSet} groups
*/
Linegraph.prototype.setGroups = function(groups) {
var me = this,
ids;
// unsubscribe from current dataset
if (this.groupsData) {
util.forEach(this.groupListeners, function (callback, event) {
me.groupsData.unsubscribe(event, callback);
});
// remove all drawn groups
ids = this.groupsData.getIds();
this.groupsData = null;
this._onRemoveGroups(ids); // note: this will cause a redraw
}
// replace the dataset
if (!groups) {
this.groupsData = null;
}
else if (groups instanceof DataSet || groups instanceof DataView) {
this.groupsData = groups;
}
else {
throw new TypeError('Data must be an instance of DataSet or DataView');
}
if (this.groupsData) {
// subscribe to new dataset
var id = this.id;
util.forEach(this.groupListeners, function (callback, event) {
me.groupsData.on(event, callback, id);
});
// draw all ms
ids = this.groupsData.getIds();
this._onAddGroups(ids);
}
this._updateUngrouped();
this.updateGraph();
this.redraw();
};
/**
* Handle updated items
* @param {Number[]} ids
* @protected
*/
Linegraph.prototype._onUpdate = function(ids) {
this.updateGraph();
this.redraw();
};
Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate;
Linegraph.prototype._onRemove = function(ids) {};
Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate;
Linegraph.prototype._onRemove = Linegraph.prototype._onUpdate;
Linegraph.prototype._onUpdateGroups = Linegraph.prototype._onUpdate;
Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdate;
Linegraph.prototype._onRemoveGroups = Linegraph.prototype._onUpdate;
/**
* Create or delete the group holding all ungrouped items. This group is used when
* there are no groups specified.
* @protected
*/
Linegraph.prototype._updateUngrouped = function() {
if (this.itemsData != null) {
var datapoints = this.itemsData.get({
filter: function (item) {return item.group === undefined;},
showInternalIds:true
});
var updateQuery = [];
for (var i = 0; i < datapoints.length; i++) {
updateQuery.push({id:datapoints[i].id, group: 'graph'});
}
this.itemsData.update(updateQuery);
}
};
/**
* Repaint the component
* @return {boolean} Returns true if the component is resized
@ -3275,7 +3381,7 @@ Linegraph.prototype._onRemove = function(ids) {};
Linegraph.prototype.redraw = function() {
var resized = false;
if (this.lastWidth === undefined && this.width) {
if (this.lastWidth === undefined && this.width || this.lastWidth != this.width) {
resized = true;
}
// check whether zoomed (in that case we need to re-stack everything)
@ -3287,6 +3393,7 @@ Linegraph.prototype.redraw = function() {
// calculate actual size and position
this.width = this.dom.frame.offsetWidth;
this.svgLegend.style.left = (this.width - this.svgLegend.offsetWidth - 10) + "px";
// check if this component is resized
resized = this._isResized() || resized;
if (resized) {
@ -3304,42 +3411,126 @@ Linegraph.prototype.updateGraph = function () {
this._prepareSVGElements(this.svgElements);
if (this.width != 0 && this.itemsData != null) {
// get the range for the y Axis and draw it
var yRange = {start: this.itemsData.min('y').y, end: this.itemsData.max('y').y};
this.yAxis.setRange(yRange);
this.yAxis.redraw();
// look at different lines
var classes = this.itemsData.distinct('className');
if (classes.length > 0) {
for (var i = 0; i < classes.length; i++) {
this.drawGraph(classes[i], classes.length);
var groupIds = this.itemsData.distinct('group');
if (groupIds.length > 0) {
this._setYRanges(groupIds);
for (var i = 0; i < groupIds.length; i++) {
this.drawGraph(groupIds[i], i, groupIds.length);
}
}
else {
this.drawGraph('group0', 1);
this._setYRanges(groupIds);
this.drawGraph('graph', 0, 1);
}
this.drawLegend(classes);
}
// cleanup unused svg elements
this._cleanupSVGElements(this.svgElements);
};
Linegraph.prototype.drawGraph = function (className, amountOfGraphs) {
var datapoints = this.itemsData.get({filter: function (item) {
return item.className == className || !item.className;
}});
Linegraph.prototype._setYRanges = function(groupIds) {
var yAxisLeftUsed = false;
var yAxisRightUsed = false;
var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
var orientation = 'left';
if (groupIds.length > 0) {
for (var i = 0; i < groupIds.length; i++) {
orientation = 'left';
var group = this.groupsData.get(groupIds[i]);
if (group != null) {
if (group.options) {
if (group.options.yAxisOrientation == 'right') {
orientation = 'right';
}
}
}
var view = new vis.DataSet(this.itemsData.get({filter: function (item) {return item.group == groupIds[i];}}));
minVal = view.min("y").y;
maxVal = view.max("y").y;
if (orientation == 'left') {
yAxisLeftUsed = true;
if (minLeft > minVal) {minLeft = minVal;}
if (maxLeft < maxVal) {maxLeft = maxVal;}
}
else {
yAxisRightUsed = true;
if (minRight > minVal) {minRight = minVal;}
if (maxRight < maxVal) {maxRight = maxVal;}
}
delete view;
}
if (yAxisLeftUsed == true) {
this.yAxisLeft.setRange({start: minLeft, end: maxLeft});
}
if (yAxisRightUsed == true) {
this.yAxisRight.setRange({start: minRight, end: maxRight});
}
}
else {
var yRange = {start: this.itemsData.min('y').y, end: this.itemsData.max('y').y};
if (this.options.yAxisOrientation == 'left') {
this.yAxisLeft.setRange(yRange);
yAxisLeftUsed = true;
}
else {
this.yAxisRight.setRange(yRange);
yAxisRightUsed = true;
}
}
var changed = this._toggleAxisVisiblity(yAxisLeftUsed, this.yAxisLeft);
changed = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || changed;
if (changed) {
this.body.emitter.emit('change');
}
this.yAxisRight.master = !yAxisLeftUsed;
if (this.yAxisRight.master == false) {
if (yAxisRightUsed == true) {
this.yAxisLeft.lineOffset = this.yAxisRight.width;
}
this.yAxisLeft.redraw();
this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
this.yAxisRight.redraw();
}
else {
this.yAxisRight.redraw();
}
}
Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) {
var changed = false;
if (axisUsed == false) {
if (axis.dom.frame.parentNode) {
axis.hide();
changed = true;
}
}
else {
if (!axis.dom.frame.parentNode) {
axis.show();
changed = true;
}
}
return changed;
}
Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) {
var datapoints = this.itemsData.get({filter: function (item) {return item.group == groupId;}});
var options = this._getGroupOptions(groupId, groupIndex);
if (this.options.style == 'bar') {
this.drawBarGraph(datapoints, className, amountOfGraphs);
this.drawBarGraph(datapoints, options, amountOfGraphs);
}
else {
this.drawLineGraph(datapoints, className);
this.drawLineGraph(datapoints, options);
}
};
Linegraph.prototype.drawBarGraph = function (datapoints, className, amountOfGraphs) {
Linegraph.prototype.drawBarGraph = function (datapoints, options, amountOfGraphs) {
if (datapoints != null) {
if (datapoints.length > 0) {
var dataset = this._prepareData(datapoints);
@ -3351,7 +3542,6 @@ Linegraph.prototype.drawBarGraph = function (datapoints, className, amountOfGrap
}
}
};
Linegraph.prototype.drawBar = function (x, y, className) {
var width = 10;
rect = this._getSVGElement('rect',this.svgElements, this.svg);
@ -3363,14 +3553,14 @@ Linegraph.prototype.drawBar = function (x, y, className) {
rect.setAttributeNS(null, "class", className + " point");
};
Linegraph.prototype.drawLineGraph = function (datapoints, className) {
Linegraph.prototype.drawLineGraph = function (datapoints, options) {
if (datapoints != null) {
if (datapoints.length > 0) {
var dataset = this._prepareData(datapoints);
var dataset = this._prepareData(datapoints, options);
var path, d;
path = this._getSVGElement('path', this.svgElements, this.svg);
path.setAttributeNS(null, "class", className);
path.setAttributeNS(null, "class", options.className);
// construct path from dataset
@ -3382,43 +3572,40 @@ Linegraph.prototype.drawLineGraph = function (datapoints, className) {
}
// append with points for fill and finalize the path
if (this.options.shaded.enabled == true) {
if (options.shaded.enabled == true) {
var fillPath = this._getSVGElement('path',this.svgElements, this.svg);
if (this.options.shaded.orientation == 'top') {
if (options.shaded.orientation == 'top') {
var dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0;
}
else {
var dFill = "M" + dataset[0].x + "," + this.svg.offsetHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + this.svg.offsetHeight;
}
fillPath.setAttributeNS(null, "class", className + " fill");
fillPath.setAttributeNS(null, "class", options.className + " fill");
fillPath.setAttributeNS(null, "d", dFill);
}
// copy properties to path for drawing.
path.setAttributeNS(null, "d", "M" + d);
// draw points
if (this.options.drawPoints.enabled == true) {
this.drawPoints(dataset, className, this.svgElements, this.svg);
if (options.drawPoints.enabled == true) {
this.drawPoints(dataset, options, this.svgElements, this.svg);
}
}
}
};
Linegraph.prototype.drawPoints = function (dataset, className, container, svg) {
Linegraph.prototype.drawPoints = function (dataset, options, container, svg) {
for (var i = 0; i < dataset.length; i++) {
this.drawPoint(dataset[i].x, dataset[i].y, className, container, svg);
this.drawPoint(dataset[i].x, dataset[i].y, options, container, svg);
}
};
Linegraph.prototype.drawPoint = function(x, y, className, container, svg) {
Linegraph.prototype.drawPoint = function(x, y, options, container, svg) {
var point;
if (this.options.drawPoints.style == 'circle') {
if (options.drawPoints.style == 'circle') {
point = this._getSVGElement('circle',container,svg);
point.setAttributeNS(null, "cx", x);
point.setAttributeNS(null, "cy", y);
point.setAttributeNS(null, "r", 0.5 * this.options.drawPoints.size);
point.setAttributeNS(null, "class", className + " point");
point.setAttributeNS(null, "r", 0.5 * options.drawPoints.size);
point.setAttributeNS(null, "class", options.className + " point");
}
else {
point = this._getSVGElement('rect',container,svg);
@ -3426,37 +3613,35 @@ Linegraph.prototype.drawPoint = function(x, y, className, container, svg) {
point.setAttributeNS(null, "y", y - 0.5*this.options.drawPoints.size);
point.setAttributeNS(null, "width", this.options.drawPoints.size);
point.setAttributeNS(null, "height", this.options.drawPoints.size);
point.setAttributeNS(null, "class", className + " point");
point.setAttributeNS(null, "class", options.className + " point");
}
return point;
}
Linegraph.prototype._getSVGElement = function (elementType, container, svg) {
Linegraph.prototype._getSVGElement = function (elementType, JSONcontainer, svgContainer) {
var element;
// allocate SVG element, if it doesnt yet exist, create one.
if (container.hasOwnProperty(elementType)) { // this element has been created before
if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
// check if there is an redundant element
if (container[elementType].redundant.length > 0) {
element = container[elementType].redundant[0];
container[elementType].redundant.shift()
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);
svg.appendChild(element);
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);
container[elementType] = {used: [], redundant: []};
svg.appendChild(element);
JSONcontainer[elementType] = {used: [], redundant: []};
svgContainer.appendChild(element);
}
container[elementType].used.push(element);
JSONcontainer[elementType].used.push(element);
return element;
};
Linegraph.prototype._cleanupSVGElements = function(container) {
// cleanup the redundant svgElements;
for (var elementType in container) {
@ -3468,7 +3653,6 @@ Linegraph.prototype._cleanupSVGElements = function(container) {
}
}
};
Linegraph.prototype._prepareSVGElements = function(container) {
// cleanup the redundant svgElements;
for (var elementType in container) {
@ -3478,21 +3662,44 @@ Linegraph.prototype._prepareSVGElements = function(container) {
}
}
};
Linegraph.prototype._prepareData = function (dataset) {
Linegraph.prototype._prepareData = function (dataset, options) {
var extractedData = [];
var xValue, yValue;
var axis = this.yAxisLeft;
var toScreen = this.body.util.toScreen;
if (options.yAxisOrientation == 'right') {
axis = this.yAxisRight;
}
for (var i = 0; i < dataset.length; i++) {
xValue = this.body.util.toScreen(new Date(dataset[i].x)) + this.width;
yValue = this.yAxis.convertValue(dataset[i].y);
xValue = toScreen(new Date(dataset[i].x)) + this.width;
yValue = axis.convertValue(dataset[i].y);
extractedData.push({x: xValue, y: yValue});
}
// extractedData.sort(function (a,b) {return a.x - b.x;});
return extractedData;
};
Linegraph.prototype._getGroupOptions = function(groupId, groupIndex) {
var group = this.groupsData.get(groupId);
var options = util.copyObject(this.options, {});
options.className = 'graphGroup' + groupIndex%10;
if (group != null) {
if (group.className) {
options.className = group.className;
}
if (group.options) {
var fields = ['yAxisOrientation'];
util.selectiveExtend(fields, options, group.options);
this._mergeOptions(options, group.options,'catmullRom');
this._mergeOptions(options, group.options,'drawPoints');
this._mergeOptions(options, group.options,'shaded');
}
}
return options;
}
Linegraph.prototype._catmullRomUniform = function(data) {
// catmull rom
var p0, p1, p2, p3, bp1, bp2;
@ -3613,7 +3820,6 @@ Linegraph.prototype._linear = function(data) {
return d;
};
Linegraph.prototype.drawLegend = function(classes) {
this._prepareSVGElements(this.svgLegendElements);
var x = 0;
@ -3677,6 +3883,8 @@ Linegraph.prototype.drawLegend = function(classes) {
}
/**
* @constructor DataStep
* The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
@ -3703,7 +3911,7 @@ Linegraph.prototype.drawLegend = function(classes) {
* @param {Date} [end] The end date
* @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
*/
function DataStep(start, end, minimumStep, containerHeight) {
function DataStep(start, end, minimumStep, containerHeight, forcedStepSize) {
// variables
this.current = 0;
@ -3718,7 +3926,7 @@ function DataStep(start, end, minimumStep, containerHeight) {
this.majorSteps = [1, 2, 5, 10];
this.minorSteps = [0.25, 0.5, 1, 2];
this.setRange(start, end, minimumStep, containerHeight);
this.setRange(start, end, minimumStep, containerHeight, forcedStepSize);
}
@ -3733,12 +3941,12 @@ function DataStep(start, end, minimumStep, containerHeight) {
* @param {Number} [end] The end date and time.
* @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
*/
DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight) {
DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, forcedStepSize) {
this._start = start;
this._end = end;
this.setFirst();
if (this.autoScale) {
this.setMinimumStep(minimumStep, containerHeight);
this.setMinimumStep(minimumStep, containerHeight, forcedStepSize);
}
};
@ -3832,6 +4040,16 @@ DataStep.prototype.next = function() {
}
};
/**
* Do the next step
*/
DataStep.prototype.previous = function() {
this.current += this.step;
this.marginEnd += this.step;
this.marginRange = this.marginEnd - this.marginStart;
};
/**
* Get the current datetime
@ -9351,6 +9569,28 @@ Graph2d.prototype.setItems = function(items) {
};
/**
* Set groups
* @param {vis.DataSet | Array | google.visualization.DataTable} groups
*/
Graph2d.prototype.setGroups = function(groups) {
// convert to type DataSet when needed
var newDataSet;
if (!groups) {
newDataSet = null;
}
else if (groups instanceof DataSet || groups instanceof DataView) {
newDataSet = groups;
}
else {
// turn an array into a dataset
newDataSet = new DataSet(groups);
}
this.groupsData = newDataSet;
this.linegraph.setGroups(newDataSet);
};
/**
* Clear the Graph2d. By Default, items, groups and options are cleared.

+ 1
- 1
dist/vis.min.css
File diff suppressed because it is too large
View File


+ 12
- 12
dist/vis.min.js
File diff suppressed because it is too large
View File


+ 72
- 17
examples/Graph2d/01_basic.html View File

@ -16,23 +16,76 @@
<div id="visualization"></div>
<script type="text/javascript">
var options = {
yAxisOrientation: 'left',
shaded: {
enabled: true,
orientation: 'bottom' // top, bottom
},
drawPoints: {
enabled: false,
size: 6,
style: 'circle' // square, circle
}
};
// create a data set with groups
var names = ['John', 'Alston', 'Lee', 'Grant'];
var groups = new vis.DataSet();
groups.add({
id: 0,
content: names[0],
className: "graphGroup9",
options: {
drawPoints: {
style: 'square' // square, circle
},
shaded: {
orientation: 'bottom' // top, bottom
}
}});
groups.add({
id: 1,
content: names[1],
className: "graphGroup1",
options: {
yAxisOrientation: 'right',
drawPoints: true, // square, circle
shaded: {
orientation: 'top' // top, bottom
}
}});
groups.add({
id: 2,
content: names[2],
className: "graphGroup2",
options: {
shaded: {
enabled: false,
orientation: 'top' // top, bottom
}
}});
var container = document.getElementById('visualization');
var items = [
{x: '2014-06-11', y: 10, className: 'group0', label: 'red'},
{x: '2014-06-12', y: 25, className: 'group0', label: 'red'},
{x: '2014-06-13', y: 30, className: 'group0', label: 'red'},
{x: '2014-06-14', y: 10, className: 'group1', label: 'green'},
{x: '2014-06-15', y: 15, className: 'group1', label: 'green'},
{x: '2014-06-16', y: 30, className: 'group1', label: 'green'},
{x: '2014-06-17', y: 10, className: 'group2', label: 'green'},
{x: '2014-06-18', y: 15, className: 'group2', label: 'green'},
{x: '2014-06-19', y: 50, className: 'group2', label: 'green'},
{x: '2014-06-20', y: 10, className: 'group3', label: 'green'},
{x: '2014-06-21', y: 19, className: 'group3', label: 'green'},
{x: '2014-06-22', y: 60, className: 'group3', label: 'green'},
{x: '2014-06-23', y: 10, className: 'group4', label: 'red'},
{x: '2014-06-24', y: 25, className: 'group4', label: 'red'},
{x: '2014-06-25', y: 30, className: 'group4', label: 'red'}
{x: '2014-06-11', y: 10 },
{x: '2014-06-12', y: 25 },
{x: '2014-06-13', y: 30, group: 0},
{x: '2014-06-14', y: 10, group: 0},
{x: '2014-06-15', y: 150, group: 1},
{x: '2014-06-16', y: 300, group: 1},
{x: '2014-06-17', y: 100, group: 1},
{x: '2014-06-18', y: 150, group: 1},
{x: '2014-06-19', y: 520, group: 1},
{x: '2014-06-20', y: 100, group: 1},
{x: '2014-06-21', y: 19, group: 2},
{x: '2014-06-22', y: 60, group: 2},
{x: '2014-06-23', y: 10, group: 2},
{x: '2014-06-24', y: 25, group: 2},
{x: '2014-06-25', y: 30, group: 2}
// {x: '2014-06-11', y: 10},
// {x: '2014-06-12', y: 25},
// {x: '2014-06-13', y: 30},
@ -42,8 +95,10 @@
];
var dataset = new vis.DataSet(items);
var options = {};
var graph2d = new vis.Graph2d(container, dataset, options);
var graph2d = new vis.Graph2d(container);
graph2d.setOptions(options);
graph2d.setGroups(groups);
graph2d.setItems(dataset);
</script>
</body>

+ 14
- 4
src/timeline/DataStep.js View File

@ -24,7 +24,7 @@
* @param {Date} [end] The end date
* @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
*/
function DataStep(start, end, minimumStep, containerHeight) {
function DataStep(start, end, minimumStep, containerHeight, forcedStepSize) {
// variables
this.current = 0;
@ -39,7 +39,7 @@ function DataStep(start, end, minimumStep, containerHeight) {
this.majorSteps = [1, 2, 5, 10];
this.minorSteps = [0.25, 0.5, 1, 2];
this.setRange(start, end, minimumStep, containerHeight);
this.setRange(start, end, minimumStep, containerHeight, forcedStepSize);
}
@ -54,12 +54,12 @@ function DataStep(start, end, minimumStep, containerHeight) {
* @param {Number} [end] The end date and time.
* @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
*/
DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight) {
DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, forcedStepSize) {
this._start = start;
this._end = end;
this.setFirst();
if (this.autoScale) {
this.setMinimumStep(minimumStep, containerHeight);
this.setMinimumStep(minimumStep, containerHeight, forcedStepSize);
}
};
@ -153,6 +153,16 @@ DataStep.prototype.next = function() {
}
};
/**
* Do the next step
*/
DataStep.prototype.previous = function() {
this.current += this.step;
this.marginEnd += this.step;
this.marginRange = this.marginEnd - this.marginStart;
};
/**
* Get the current datetime

+ 22
- 0
src/timeline/Graph2d.js View File

@ -290,6 +290,28 @@ Graph2d.prototype.setItems = function(items) {
};
/**
* Set groups
* @param {vis.DataSet | Array | google.visualization.DataTable} groups
*/
Graph2d.prototype.setGroups = function(groups) {
// convert to type DataSet when needed
var newDataSet;
if (!groups) {
newDataSet = null;
}
else if (groups instanceof DataSet || groups instanceof DataView) {
newDataSet = groups;
}
else {
// turn an array into a dataset
newDataSet = new DataSet(groups);
}
this.groupsData = newDataSet;
this.linegraph.setGroups(newDataSet);
};
/**
* Clear the Graph2d. By Default, items, groups and options are cleared.

+ 86
- 51
src/timeline/component/DataAxis.js View File

@ -13,6 +13,8 @@ function DataAxis (body, options) {
orientation: 'left', // supported: 'left'
showMinorLabels: true,
showMajorLabels: true,
majorLinesOffset: 25,
minorLinesOffset: 25,
width: '50px',
height: '300px'
};
@ -47,15 +49,32 @@ function DataAxis (body, options) {
this.width = Number(this.options.width.replace("px",""));
this.height = Number(this.options.height.replace("px",""));
this.stepPixels = 25;
this.stepPixelsForced = 25;
this.lineOffset = 0;
this.master = true;
// create the HTML DOM
this._create();
}
DataAxis.prototype = new Component();
//DataAxis.prototype.setOptions = function(options) {
//
//}
DataAxis.prototype.setOptions = function(options) {
if (options) {
var redraw = false;
if (this.options.orientation != options.orientation && options.orientation !== undefined) {
redraw = true;
}
var fields = ['orientation','showMinorLabels','showMajorLabels','width','height'];
util.selectiveExtend(fields, this.options, options);
if (redraw == true && this.dom.frame) {
this.hide();
this.show();
}
}
}
/**
@ -69,8 +88,6 @@ DataAxis.prototype._create = function() {
this.dom.lineContainer = document.createElement('div');
this.dom.lineContainer.style.width = '100%';
this.dom.lineContainer.style.height = this.options.height;
this.show();
};
@ -82,7 +99,7 @@ DataAxis.prototype.show = function() {
if (this.options.orientation == 'left') {
this.body.dom.left.appendChild(this.dom.frame);
}
if (this.options.orientation == 'right') {
else {
this.body.dom.right.appendChild(this.dom.frame);
}
}
@ -139,9 +156,9 @@ DataAxis.prototype.redraw = function () {
props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth;
props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2*this.options.minorLinesOffset;
props.minorLineHeight = 1;
props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth;
props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2*this.options.majorLinesOffset;;
props.majorLineHeight = 1;
// take frame offline while updating (is almost twice as fast)
@ -192,13 +209,27 @@ DataAxis.prototype._redrawLabels = function () {
step.first();
var stepPixels = this.dom.frame.offsetHeight / ((step.marginRange / step.step) + 1);
var xFirstMajorLabel = undefined;
this.stepPixels = stepPixels;
var amountOfSteps = this.height / stepPixels;
var stepDifference = 0;
if (this.master == false) {
stepPixels = this.stepPixelsForced;
stepDifference = Math.round((this.height / stepPixels) - amountOfSteps);
for (var i = 0; i < 0.5 * stepDifference; i++) {
step.previous();
}
amountOfSteps = this.height / stepPixels;
}
var xFirstMajorLabel = undefined;
this.valueAtZero = step.marginEnd;
var marginStartPos = 0;
var max = 0;
while (step.hasNext() && max < 1000) {
var y = Math.round(max * stepPixels);
var y = 0;
while (max < amountOfSteps) {
y = Math.round(max * stepPixels);
marginStartPos = max * stepPixels;
var isMajor = step.isMajor();
@ -207,7 +238,7 @@ DataAxis.prototype._redrawLabels = function () {
}
if (isMajor && this.options['showMajorLabels']) {
if (y > 0) {
if (y >= 0) {
if (xFirstMajorLabel == undefined) {
xFirstMajorLabel = y;
}
@ -223,7 +254,7 @@ DataAxis.prototype._redrawLabels = function () {
max++;
}
this.conversionFactor = marginStartPos/step.marginRange;
this.conversionFactor = marginStartPos/((amountOfSteps-1) * step.step);
// create a major label on the left when needed
if (this.options['showMajorLabels']) {
@ -257,7 +288,7 @@ DataAxis.prototype.convertValues = function(data) {
DataAxis.prototype.convertValue = function(value) {
var invertedValue = this.valueAtZero - value;
var convertedValue = invertedValue * this.conversionFactor;
return convertedValue - 2; // the -2 is to compensate for the borders
return convertedValue; // the -2 is to compensate for the borders
}
/**
@ -325,7 +356,7 @@ DataAxis.prototype._redrawMajorText = function (y, text, orientation) {
label.style.textAlign = "right";
}
else {
label.style.left = '2';
label.style.left = '2px';
label.style.textAlign = "left";
}
@ -339,27 +370,29 @@ DataAxis.prototype._redrawMajorText = function (y, text, orientation) {
* @private
*/
DataAxis.prototype._redrawMinorLine = function (y, orientation) {
// reuse redundant line
var line = this.dom.redundant.minorLines.shift();
if (!line) {
// create vertical line
line = document.createElement('div');
line.className = 'grid horizontal minor';
this.dom.lineContainer.appendChild(line);
}
this.dom.minorLines.push(line);
if (this.master == true) {
// reuse redundant line
var line = this.dom.redundant.minorLines.shift();
if (!line) {
// create vertical line
line = document.createElement('div');
line.className = 'grid horizontal minor';
this.dom.lineContainer.appendChild(line);
}
this.dom.minorLines.push(line);
var props = this.props;
if (orientation == 'left') {
line.style.left = (this.width - 15) + 'px';
}
else {
line.style.left = -1*(this.width - 15) + 'px';
}
var props = this.props;
if (orientation == 'left') {
line.style.left = (this.width - this.options.minorLinesOffset) + 'px';
}
else {
line.style.left = -1*(this.width - this.options.minorLinesOffset) + 'px';
}
line.style.width = props.minorLineWidth + 'px';
line.style.top = y + 'px';
line.style.width = props.minorLineWidth + 'px';
line.style.top = y + 'px';
}
};
/**
@ -369,25 +402,27 @@ DataAxis.prototype._redrawMinorLine = function (y, orientation) {
* @private
*/
DataAxis.prototype._redrawMajorLine = function (y, orientation) {
// reuse redundant line
var line = this.dom.redundant.majorLines.shift();
if (!line) {
// create vertical line
line = document.createElement('div');
line.className = 'grid horizontal major';
this.dom.lineContainer.appendChild(line);
}
this.dom.majorLines.push(line);
if (this.master == true) {
// reuse redundant line
var line = this.dom.redundant.majorLines.shift();
if (!line) {
// create vertical line
line = document.createElement('div');
line.className = 'grid horizontal major';
this.dom.lineContainer.appendChild(line);
}
this.dom.majorLines.push(line);
if (orientation == 'left') {
line.style.left = (this.width - 25) + 'px';
}
else {
line.style.left = -1*(this.width - 25) + 'px';
if (orientation == 'left') {
line.style.left = (this.width - this.options.majorLinesOffset) + 'px';
}
else {
line.style.left = -1*(this.width - this.options.mayorLinesOffset) + 'px';
}
line.style.top = y + 'px';
line.style.width = this.props.majorLineWidth + 'px';
}
line.style.top = y + 'px';
line.style.width = this.props.majorLineWidth + 'px';
};

+ 269
- 100
src/timeline/component/Linegraph.js View File

@ -5,6 +5,7 @@ function Linegraph(body, options) {
this.body = body;
this.defaultOptions = {
yAxisOrientation: 'left',
shaded: {
enabled: true,
orientation: 'top' // top, bottom
@ -48,6 +49,19 @@ function Linegraph(body, options) {
}
};
// listeners for the DataSet of the groups
this.groupListeners = {
'add': function (event, params, senderId) {
me._onAddGroups(params.items);
},
'update': function (event, params, senderId) {
me._onUpdateGroups(params.items);
},
'remove': function (event, params, senderId) {
me._onRemoveGroups(params.items);
}
};
this.items = {}; // object with an Item for every data item
this.selection = []; // list with the ids of all selected nodes
this.lastStart = this.body.range.start;
@ -55,11 +69,10 @@ function Linegraph(body, options) {
this.svgElements = {};
this.svgLegendElements = {};
// create the HTML DOM
this.setOptions(options);
this._create();
var me = this;
var me = this;
this.body.emitter.on("rangechange",function() {
if (me.lastStart != 0) {
var offset = me.body.range.start - me.lastStart;
@ -77,7 +90,8 @@ function Linegraph(body, options) {
me.updateGraph.apply(me);
});
this.setOptions(options);
// create the HTML DOM
this._create();
this.body.emitter.emit("change");
}
@ -109,10 +123,22 @@ Linegraph.prototype._create = function(){
frame.appendChild(this.svgLegend);
// panel with time axis
this.yAxis = new DataAxis(this.body, {
this.yAxisLeft = new DataAxis(this.body, {
orientation: 'left',
showMinorLabels: true,
showMajorLabels: true,
majorLinesOffset: 25,
minorLinesOffset: 25,
width: '50px',
height: this.svg.style.height
});
this.yAxisRight = new DataAxis(this.body, {
orientation: 'right',
showMinorLabels: true,
showMajorLabels: true,
majorLinesOffset: 25,
minorLinesOffset: 25,
width: '50px',
height: this.svg.style.height
});
@ -120,22 +146,13 @@ Linegraph.prototype._create = function(){
this.show();
};
Linegraph.prototype.setOptions = function(options) {
if (options) {
var fields = ['barGraph','catmullRom','shaded','drawPoints'];
var fields = ['yAxisOrientation'];
util.selectiveExtend(fields, this.options, options);
if (options.catmullRom) {
if (typeof options.catmullRom == 'boolean') {
this.options.catmullRom.enabled = options.catmullRom;
}
else {
this.options.catmullRom.enabled = true;
for (var prop in options.catmullRom) {
if (options.catmullRom.hasOwnProperty(prop)) {
this.options.catmullRom[prop] = options.catmullRom[prop];
}
}
if (typeof options.catmullRom == 'object') {
if (options.catmullRom.parametrization) {
if (options.catmullRom.parametrization == 'uniform') {
this.options.catmullRom.alpha = 0;
@ -150,36 +167,27 @@ Linegraph.prototype.setOptions = function(options) {
}
}
}
this._mergeOptions(this.options, options,'catmullRom');
this._mergeOptions(this.options, options,'drawPoints');
this._mergeOptions(this.options, options,'shaded');
}
};
if (options.drawPoints) {
if (typeof options.catmullRom == 'boolean') {
this.options.drawPoints.enabled = options.drawPoints;
}
else {
this.options.drawPoints.enabled = true;
for (prop in options.drawPoints) {
if (options.drawPoints.hasOwnProperty(prop)) {
this.options.drawPoints[prop] = options.drawPoints[prop];
}
}
}
Linegraph.prototype._mergeOptions = function (mergeTarget, options,option) {
if (options[option]) {
if (typeof options[option] == 'boolean') {
mergeTarget[option].enabled = options[option];
}
if (options.shaded) {
if (typeof options.shaded == 'boolean') {
this.options.shaded.enabled = options.shaded;
}
else {
this.options.shaded.enabled = true;
for (prop in options.shaded) {
if (options.shaded.hasOwnProperty(prop)) {
this.options.shaded[prop] = options.shaded.drawPoints[prop];
}
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
@ -203,8 +211,6 @@ Linegraph.prototype.show = function() {
};
/**
* Set items
* @param {vis.DataSet | null} items
@ -247,31 +253,91 @@ Linegraph.prototype.setItems = function(items) {
ids = this.itemsData.getIds();
this._onAdd(ids);
}
this._updateUngrouped();
this.updateGraph();
this.redraw();
};
/**
* Handle added items
* @param {Number[]} ids
* @protected
* Set groups
* @param {vis.DataSet} groups
*/
Linegraph.prototype.setGroups = function(groups) {
var me = this,
ids;
// unsubscribe from current dataset
if (this.groupsData) {
util.forEach(this.groupListeners, function (callback, event) {
me.groupsData.unsubscribe(event, callback);
});
// remove all drawn groups
ids = this.groupsData.getIds();
this.groupsData = null;
this._onRemoveGroups(ids); // note: this will cause a redraw
}
// replace the dataset
if (!groups) {
this.groupsData = null;
}
else if (groups instanceof DataSet || groups instanceof DataView) {
this.groupsData = groups;
}
else {
throw new TypeError('Data must be an instance of DataSet or DataView');
}
if (this.groupsData) {
// subscribe to new dataset
var id = this.id;
util.forEach(this.groupListeners, function (callback, event) {
me.groupsData.on(event, callback, id);
});
// draw all ms
ids = this.groupsData.getIds();
this._onAddGroups(ids);
}
this._updateUngrouped();
this.updateGraph();
this.redraw();
};
/**
* Handle updated items
* @param {Number[]} ids
* @protected
*/
Linegraph.prototype._onUpdate = function(ids) {
this.updateGraph();
this.redraw();
};
Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate;
Linegraph.prototype._onRemove = function(ids) {};
Linegraph.prototype._onAdd = Linegraph.prototype._onUpdate;
Linegraph.prototype._onRemove = Linegraph.prototype._onUpdate;
Linegraph.prototype._onUpdateGroups = Linegraph.prototype._onUpdate;
Linegraph.prototype._onAddGroups = Linegraph.prototype._onUpdate;
Linegraph.prototype._onRemoveGroups = Linegraph.prototype._onUpdate;
/**
* Create or delete the group holding all ungrouped items. This group is used when
* there are no groups specified.
* @protected
*/
Linegraph.prototype._updateUngrouped = function() {
if (this.itemsData != null) {
var datapoints = this.itemsData.get({
filter: function (item) {return item.group === undefined;},
showInternalIds:true
});
var updateQuery = [];
for (var i = 0; i < datapoints.length; i++) {
updateQuery.push({id:datapoints[i].id, group: 'graph'});
}
this.itemsData.update(updateQuery);
}
};
/**
* Repaint the component
* @return {boolean} Returns true if the component is resized
@ -279,7 +345,7 @@ Linegraph.prototype._onRemove = function(ids) {};
Linegraph.prototype.redraw = function() {
var resized = false;
if (this.lastWidth === undefined && this.width) {
if (this.lastWidth === undefined && this.width || this.lastWidth != this.width) {
resized = true;
}
// check whether zoomed (in that case we need to re-stack everything)
@ -291,6 +357,7 @@ Linegraph.prototype.redraw = function() {
// calculate actual size and position
this.width = this.dom.frame.offsetWidth;
this.svgLegend.style.left = (this.width - this.svgLegend.offsetWidth - 10) + "px";
// check if this component is resized
resized = this._isResized() || resized;
if (resized) {
@ -308,42 +375,126 @@ Linegraph.prototype.updateGraph = function () {
this._prepareSVGElements(this.svgElements);
if (this.width != 0 && this.itemsData != null) {
// get the range for the y Axis and draw it
var yRange = {start: this.itemsData.min('y').y, end: this.itemsData.max('y').y};
this.yAxis.setRange(yRange);
this.yAxis.redraw();
// look at different lines
var classes = this.itemsData.distinct('className');
if (classes.length > 0) {
for (var i = 0; i < classes.length; i++) {
this.drawGraph(classes[i], classes.length);
var groupIds = this.itemsData.distinct('group');
if (groupIds.length > 0) {
this._setYRanges(groupIds);
for (var i = 0; i < groupIds.length; i++) {
this.drawGraph(groupIds[i], i, groupIds.length);
}
}
else {
this.drawGraph('group0', 1);
this._setYRanges(groupIds);
this.drawGraph('graph', 0, 1);
}
this.drawLegend(classes);
}
// cleanup unused svg elements
this._cleanupSVGElements(this.svgElements);
};
Linegraph.prototype.drawGraph = function (className, amountOfGraphs) {
var datapoints = this.itemsData.get({filter: function (item) {
return item.className == className || !item.className;
}});
Linegraph.prototype._setYRanges = function(groupIds) {
var yAxisLeftUsed = false;
var yAxisRightUsed = false;
var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
var orientation = 'left';
if (groupIds.length > 0) {
for (var i = 0; i < groupIds.length; i++) {
orientation = 'left';
var group = this.groupsData.get(groupIds[i]);
if (group != null) {
if (group.options) {
if (group.options.yAxisOrientation == 'right') {
orientation = 'right';
}
}
}
var view = new vis.DataSet(this.itemsData.get({filter: function (item) {return item.group == groupIds[i];}}));
minVal = view.min("y").y;
maxVal = view.max("y").y;
if (orientation == 'left') {
yAxisLeftUsed = true;
if (minLeft > minVal) {minLeft = minVal;}
if (maxLeft < maxVal) {maxLeft = maxVal;}
}
else {
yAxisRightUsed = true;
if (minRight > minVal) {minRight = minVal;}
if (maxRight < maxVal) {maxRight = maxVal;}
}
delete view;
}
if (yAxisLeftUsed == true) {
this.yAxisLeft.setRange({start: minLeft, end: maxLeft});
}
if (yAxisRightUsed == true) {
this.yAxisRight.setRange({start: minRight, end: maxRight});
}
}
else {
var yRange = {start: this.itemsData.min('y').y, end: this.itemsData.max('y').y};
if (this.options.yAxisOrientation == 'left') {
this.yAxisLeft.setRange(yRange);
yAxisLeftUsed = true;
}
else {
this.yAxisRight.setRange(yRange);
yAxisRightUsed = true;
}
}
var changed = this._toggleAxisVisiblity(yAxisLeftUsed, this.yAxisLeft);
changed = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || changed;
if (changed) {
this.body.emitter.emit('change');
}
this.yAxisRight.master = !yAxisLeftUsed;
if (this.yAxisRight.master == false) {
if (yAxisRightUsed == true) {
this.yAxisLeft.lineOffset = this.yAxisRight.width;
}
this.yAxisLeft.redraw();
this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
this.yAxisRight.redraw();
}
else {
this.yAxisRight.redraw();
}
}
Linegraph.prototype._toggleAxisVisiblity = function(axisUsed, axis) {
var changed = false;
if (axisUsed == false) {
if (axis.dom.frame.parentNode) {
axis.hide();
changed = true;
}
}
else {
if (!axis.dom.frame.parentNode) {
axis.show();
changed = true;
}
}
return changed;
}
Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) {
var datapoints = this.itemsData.get({filter: function (item) {return item.group == groupId;}});
var options = this._getGroupOptions(groupId, groupIndex);
if (this.options.style == 'bar') {
this.drawBarGraph(datapoints, className, amountOfGraphs);
this.drawBarGraph(datapoints, options, amountOfGraphs);
}
else {
this.drawLineGraph(datapoints, className);
this.drawLineGraph(datapoints, options);
}
};
Linegraph.prototype.drawBarGraph = function (datapoints, className, amountOfGraphs) {
Linegraph.prototype.drawBarGraph = function (datapoints, options, amountOfGraphs) {
if (datapoints != null) {
if (datapoints.length > 0) {
var dataset = this._prepareData(datapoints);
@ -355,7 +506,6 @@ Linegraph.prototype.drawBarGraph = function (datapoints, className, amountOfGrap
}
}
};
Linegraph.prototype.drawBar = function (x, y, className) {
var width = 10;
rect = this._getSVGElement('rect',this.svgElements, this.svg);
@ -367,14 +517,14 @@ Linegraph.prototype.drawBar = function (x, y, className) {
rect.setAttributeNS(null, "class", className + " point");
};
Linegraph.prototype.drawLineGraph = function (datapoints, className) {
Linegraph.prototype.drawLineGraph = function (datapoints, options) {
if (datapoints != null) {
if (datapoints.length > 0) {
var dataset = this._prepareData(datapoints);
var dataset = this._prepareData(datapoints, options);
var path, d;
path = this._getSVGElement('path', this.svgElements, this.svg);
path.setAttributeNS(null, "class", className);
path.setAttributeNS(null, "class", options.className);
// construct path from dataset
@ -386,43 +536,40 @@ Linegraph.prototype.drawLineGraph = function (datapoints, className) {
}
// append with points for fill and finalize the path
if (this.options.shaded.enabled == true) {
if (options.shaded.enabled == true) {
var fillPath = this._getSVGElement('path',this.svgElements, this.svg);
if (this.options.shaded.orientation == 'top') {
if (options.shaded.orientation == 'top') {
var dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0;
}
else {
var dFill = "M" + dataset[0].x + "," + this.svg.offsetHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + this.svg.offsetHeight;
}
fillPath.setAttributeNS(null, "class", className + " fill");
fillPath.setAttributeNS(null, "class", options.className + " fill");
fillPath.setAttributeNS(null, "d", dFill);
}
// copy properties to path for drawing.
path.setAttributeNS(null, "d", "M" + d);
// draw points
if (this.options.drawPoints.enabled == true) {
this.drawPoints(dataset, className, this.svgElements, this.svg);
if (options.drawPoints.enabled == true) {
this.drawPoints(dataset, options, this.svgElements, this.svg);
}
}
}
};
Linegraph.prototype.drawPoints = function (dataset, className, container, svg) {
Linegraph.prototype.drawPoints = function (dataset, options, container, svg) {
for (var i = 0; i < dataset.length; i++) {
this.drawPoint(dataset[i].x, dataset[i].y, className, container, svg);
this.drawPoint(dataset[i].x, dataset[i].y, options, container, svg);
}
};
Linegraph.prototype.drawPoint = function(x, y, className, container, svg) {
Linegraph.prototype.drawPoint = function(x, y, options, container, svg) {
var point;
if (this.options.drawPoints.style == 'circle') {
if (options.drawPoints.style == 'circle') {
point = this._getSVGElement('circle',container,svg);
point.setAttributeNS(null, "cx", x);
point.setAttributeNS(null, "cy", y);
point.setAttributeNS(null, "r", 0.5 * this.options.drawPoints.size);
point.setAttributeNS(null, "class", className + " point");
point.setAttributeNS(null, "r", 0.5 * options.drawPoints.size);
point.setAttributeNS(null, "class", options.className + " point");
}
else {
point = this._getSVGElement('rect',container,svg);
@ -430,7 +577,7 @@ Linegraph.prototype.drawPoint = function(x, y, className, container, svg) {
point.setAttributeNS(null, "y", y - 0.5*this.options.drawPoints.size);
point.setAttributeNS(null, "width", this.options.drawPoints.size);
point.setAttributeNS(null, "height", this.options.drawPoints.size);
point.setAttributeNS(null, "class", className + " point");
point.setAttributeNS(null, "class", options.className + " point");
}
return point;
}
@ -459,7 +606,6 @@ Linegraph.prototype._getSVGElement = function (elementType, JSONcontainer, svgCo
JSONcontainer[elementType].used.push(element);
return element;
};
Linegraph.prototype._cleanupSVGElements = function(container) {
// cleanup the redundant svgElements;
for (var elementType in container) {
@ -471,7 +617,6 @@ Linegraph.prototype._cleanupSVGElements = function(container) {
}
}
};
Linegraph.prototype._prepareSVGElements = function(container) {
// cleanup the redundant svgElements;
for (var elementType in container) {
@ -481,21 +626,44 @@ Linegraph.prototype._prepareSVGElements = function(container) {
}
}
};
Linegraph.prototype._prepareData = function (dataset) {
Linegraph.prototype._prepareData = function (dataset, options) {
var extractedData = [];
var xValue, yValue;
var axis = this.yAxisLeft;
var toScreen = this.body.util.toScreen;
if (options.yAxisOrientation == 'right') {
axis = this.yAxisRight;
}
for (var i = 0; i < dataset.length; i++) {
xValue = this.body.util.toScreen(new Date(dataset[i].x)) + this.width;
yValue = this.yAxis.convertValue(dataset[i].y);
xValue = toScreen(new Date(dataset[i].x)) + this.width;
yValue = axis.convertValue(dataset[i].y);
extractedData.push({x: xValue, y: yValue});
}
// extractedData.sort(function (a,b) {return a.x - b.x;});
return extractedData;
};
Linegraph.prototype._getGroupOptions = function(groupId, groupIndex) {
var group = this.groupsData.get(groupId);
var options = util.copyObject(this.options, {});
options.className = 'graphGroup' + groupIndex%10;
if (group != null) {
if (group.className) {
options.className = group.className;
}
if (group.options) {
var fields = ['yAxisOrientation'];
util.selectiveExtend(fields, options, group.options);
this._mergeOptions(options, group.options,'catmullRom');
this._mergeOptions(options, group.options,'drawPoints');
this._mergeOptions(options, group.options,'shaded');
}
}
return options;
}
Linegraph.prototype._catmullRomUniform = function(data) {
// catmull rom
var p0, p1, p2, p3, bp1, bp2;
@ -616,7 +784,6 @@ Linegraph.prototype._linear = function(data) {
return d;
};
Linegraph.prototype.drawLegend = function(classes) {
this._prepareSVGElements(this.svgLegendElements);
var x = 0;
@ -679,3 +846,5 @@ Linegraph.prototype.drawLegend = function(classes) {
this._cleanupSVGElements(this.svgLegendElements);
}

+ 10
- 10
src/timeline/component/css/pathStyles.css View File

@ -1,67 +1,67 @@
.vis.timeline .group0 {
.vis.timeline .graphGroup0 {
fill:#4f81bd;
fill-opacity:0;
stroke-width:2px;
stroke: #4f81bd;
}
.vis.timeline .group1 {
.vis.timeline .graphGroup1 {
fill:#f79646;
fill-opacity:0;
stroke-width:2px;
stroke: #f79646;
}
.vis.timeline .group2 {
.vis.timeline .graphGroup2 {
fill: #8c51cf;
fill-opacity:0;
stroke-width:2px;
stroke: #8c51cf;
}
.vis.timeline .group3 {
.vis.timeline .graphGroup3 {
fill: #75c841;
fill-opacity:0;
stroke-width:2px;
stroke: #75c841;
}
.vis.timeline .group4 {
.vis.timeline .graphGroup4 {
fill: #ff0100;
fill-opacity:0;
stroke-width:2px;
stroke: #ff0100;
}
.vis.timeline .group5 {
.vis.timeline .graphGroup5 {
fill: #37d8e6;
fill-opacity:0;
stroke-width:2px;
stroke: #37d8e6;
}
.vis.timeline .group6 {
.vis.timeline .graphGroup6 {
fill: #042662;
fill-opacity:0;
stroke-width:2px;
stroke: #042662;
}
.vis.timeline .group7 {
.vis.timeline .graphGroup7 {
fill:#00ff26;
fill-opacity:0;
stroke-width:2px;
stroke: #00ff26;
}
.vis.timeline .group8 {
.vis.timeline .graphGroup8 {
fill:#ff00ff;
fill-opacity:0;
stroke-width:2px;
stroke: #ff00ff;
}
.vis.timeline .group9 {
.vis.timeline .graphGroup9 {
fill: #8f3938;
fill-opacity:0;
stroke-width:2px;

+ 3
- 0
src/timeline/component/legend.js View File

@ -0,0 +1,3 @@
/**
* Created by Alex on 6/17/14.
*/

+ 1
- 0
src/util.js View File

@ -988,4 +988,5 @@ util.copyObject = function(objectFrom, objectTo) {
}
}
}
return objectTo;
};

Loading…
Cancel
Save