diff --git a/HISTORY.md b/HISTORY.md
index cea287db..2d72dfd8 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -11,9 +11,9 @@ http://visjs.org
When creating a custom bundle using browserify, one now needs to add a
transform step using `6to5ify`, this is described in README.md.
-
### Timeline
+- Integrated an option configurator and validator.
- Implemented option `multiselect`, which is false by default.
- Added method `setData({groups: groups, items: items})`.
- Fixed range items not being displayed smaller than 10 pixels (twice the
diff --git a/docs/timeline/index.html b/docs/timeline/index.html
index f0b78860..58a4b3df 100644
--- a/docs/timeline/index.html
+++ b/docs/timeline/index.html
@@ -477,6 +477,13 @@ var options = {
When active, a blue shadow border is displayed around the Timeline. The Timeline is set active by clicking on it, and is changed to inactive again by clicking outside the Timeline or by pressing the ESC key.
+
+ configure |
+ boolean |
+ false |
+ When true, a configurator is loaded where all configuration options of the Timeline can be changed live. |
+
+
dataAttributes |
string[] or 'all' |
diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js
index 906b829d..2e0fb77c 100644
--- a/lib/timeline/Core.js
+++ b/lib/timeline/Core.js
@@ -13,9 +13,6 @@ var CustomTime = require('./component/CustomTime');
/**
* Create a timeline visualization
- * @param {HTMLElement} container
- * @param {vis.DataSet | Array} [items]
- * @param {Object} [options] See Core.setOptions for the available options.
* @constructor
*/
function Core () {}
@@ -284,6 +281,18 @@ Core.prototype.setOptions = function (options) {
// propagate options to all components
this.components.forEach(component => component.setOptions(options));
+ // enable/disable configure
+ if (this.configurationSystem) {
+ this.configurationSystem.setOptions(options.configure);
+
+ // collect the settings of all components, and pass them to the configuration system
+ var appliedOptions = util.deepExtend({}, this.options);
+ this.components.forEach(function (component) {
+ util.deepExtend(appliedOptions, component.options);
+ });
+ this.configurationSystem.setModuleOptions({global: appliedOptions});
+ }
+
// redraw everything
this._redraw();
};
diff --git a/lib/timeline/TimeStep.js b/lib/timeline/TimeStep.js
index 5313ace1..88d25d7a 100644
--- a/lib/timeline/TimeStep.js
+++ b/lib/timeline/TimeStep.js
@@ -80,7 +80,7 @@ TimeStep.FORMAT = {
/**
* Set custom formatting for the minor an major labels of the TimeStep.
* Both `minorLabels` and `majorLabels` are an Object with properties:
- * 'millisecond, 'second, 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
+ * 'millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'month', 'year'.
* @param {{minorLabels: Object, majorLabels: Object}} format
*/
TimeStep.prototype.setFormat = function (format) {
@@ -246,7 +246,7 @@ TimeStep.prototype.getCurrent = function() {
* @param {{scale: string, step: number}} params
* An object containing two properties:
* - A string 'scale'. Choose from 'millisecond', 'second',
- * 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
+ * 'minute', 'hour', 'weekday', 'day', 'month', 'year'.
* - A number 'step'. A step size, by default 1.
* Choose for example 1, 2, 5, or 10.
*/
@@ -324,7 +324,7 @@ TimeStep.prototype.setMinimumStep = function(minimumStep) {
* Static function
* @param {Date} date the date to be snapped.
* @param {string} scale Current scale, can be 'millisecond', 'second',
- * 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
+ * 'minute', 'hour', 'weekday, 'day', 'month', 'year'.
* @param {number} step Current step (1, 2, 4, 5, ...
* @return {Date} snappedDate
*/
diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js
index e4b25613..e61b73f6 100644
--- a/lib/timeline/Timeline.js
+++ b/lib/timeline/Timeline.js
@@ -10,6 +10,12 @@ var CurrentTime = require('./component/CurrentTime');
var CustomTime = require('./component/CustomTime');
var ItemSet = require('./component/ItemSet');
+var ConfigurationSystem = require('../network/modules/ConfigurationSystem');
+var Validator = require('../network/modules/Validator').default;
+var printStyle = require('../network/modules/Validator').printStyle;
+var allOptions = require('./options').allOptions;
+var configureOptions = require('./options').configureOptions;
+
/**
* Create a timeline visualization
* @param {HTMLElement} container
@@ -111,6 +117,9 @@ function Timeline (container, items, groups, options) {
me.emit('contextmenu', me.getEventProperties(event))
};
+ // setup configuration system
+ this.configurationSystem = new ConfigurationSystem(this, container, configureOptions);
+
// apply options
if (options) {
this.setOptions(options);
@@ -143,6 +152,30 @@ Timeline.prototype.redraw = function() {
this._redraw();
};
+Timeline.prototype.setOptions = function (options) {
+ // validate options
+ let errorFound = Validator.validate(options, allOptions);
+ if (errorFound === true) {
+ options = {};
+ console.log('%cErrors have been found in the supplied options object. None of the options will be used.', printStyle);
+ }
+
+ Core.prototype.setOptions.call(this, options);
+
+ if ('type' in options) {
+ if (options.type !== this.options.type) {
+ this.options.type = options.type;
+
+ // force recreation of all items
+ var itemsData = this.itemsData;
+ if (itemsData) {
+ this.setItems(null); // remove all
+ this.setItems(itemsData); // add all
+ }
+ }
+ }
+};
+
/**
* Set items
* @param {vis.DataSet | Array | null} items
@@ -317,9 +350,9 @@ Timeline.prototype.focus = function(id, options) {
*/
Timeline.prototype.getItemRange = function() {
// calculate min from start filed
- var dataset = this.itemsData.getDataSet(),
- min = null,
- max = null;
+ var dataset = this.itemsData && this.itemsData.getDataSet();
+ var min = null;
+ var max = null;
if (dataset) {
// calculate the minimum value of the field 'start'
diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js
index 487bdcf1..0480786d 100644
--- a/lib/timeline/component/ItemSet.js
+++ b/lib/timeline/component/ItemSet.js
@@ -29,7 +29,9 @@ function ItemSet(body, options) {
this.defaultOptions = {
type: null, // 'box', 'point', 'range', 'background'
- orientation: 'bottom', // item orientation: 'top' or 'bottom'
+ orientation: {
+ item: 'bottom' // item orientation: 'top' or 'bottom'
+ },
align: 'auto', // alignment of box items
stack: true,
groupOrder: null,
@@ -222,7 +224,7 @@ ItemSet.prototype._create = function(){
* Alignment for the items, only applicable for
* BoxItem. Choose 'center' (default), 'left', or
* 'right'.
- * {String} orientation
+ * {String} orientation.item
* Orientation of the item set. Choose 'top' or
* 'bottom' (default).
* {Function} groupOrder
@@ -282,10 +284,10 @@ ItemSet.prototype.setOptions = function(options) {
if ('orientation' in options) {
if (typeof options.orientation === 'string') {
- this.options.orientation = options.orientation;
+ this.options.orientation.item = options.orientation === 'top' ? 'top' : 'bottom';
}
else if (typeof options.orientation === 'object' && 'item' in options.orientation) {
- this.options.orientation = options.orientation.item;
+ this.options.orientation.item = options.orientation.item;
}
}
@@ -504,7 +506,7 @@ ItemSet.prototype.redraw = function() {
range = this.body.range,
asSize = util.option.asSize,
options = this.options,
- orientation = options.orientation,
+ orientation = options.orientation.item,
resized = false,
frame = this.dom.frame,
editable = options.editable.updateTime || options.editable.updateGroup;
@@ -578,7 +580,7 @@ ItemSet.prototype.redraw = function() {
* @private
*/
ItemSet.prototype._firstGroup = function() {
- var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
+ var firstGroupIndex = (this.options.orientation.item == 'top') ? 0 : (this.groupIds.length - 1);
var firstGroupId = this.groupIds[firstGroupIndex];
var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
@@ -1593,7 +1595,7 @@ ItemSet.prototype.groupFromTarget = function(event) {
return group;
}
- if (this.options.orientation === 'top') {
+ if (this.options.orientation.item === 'top') {
if (i === this.groupIds.length - 1 && pageY > top) {
return group;
}
diff --git a/lib/timeline/component/TimeAxis.js b/lib/timeline/component/TimeAxis.js
index ad60c8ef..2c81a7bb 100644
--- a/lib/timeline/component/TimeAxis.js
+++ b/lib/timeline/component/TimeAxis.js
@@ -34,10 +34,12 @@ function TimeAxis (body, options) {
};
this.defaultOptions = {
- orientation: 'bottom', // axis orientation: 'top' or 'bottom'
+ orientation: {
+ axis: 'bottom'
+ }, // axis orientation: 'top' or 'bottom'
showMinorLabels: true,
showMajorLabels: true,
- format: null,
+ format: TimeStep.FORMAT,
timeAxis: null
};
this.options = util.extend({}, this.defaultOptions);
@@ -56,7 +58,7 @@ TimeAxis.prototype = new Component();
* Set options for the TimeAxis.
* Parameters will be merged in current options.
* @param {Object} options Available options:
- * {string} [orientation]
+ * {string} [orientation.axis]
* {boolean} [showMinorLabels]
* {boolean} [showMajorLabels]
*/
@@ -67,16 +69,18 @@ TimeAxis.prototype.setOptions = function(options) {
'showMinorLabels',
'showMajorLabels',
'hiddenDates',
- 'format',
'timeAxis'
], this.options, options);
+ // deep copy the format options
+ util.selectiveDeepExtend(['format'], this.options, options);
+
if ('orientation' in options) {
if (typeof options.orientation === 'string') {
- this.options.orientation = options.orientation;
+ this.options.orientation.axis = options.orientation;
}
else if (typeof options.orientation === 'object' && 'axis' in options.orientation) {
- this.options.orientation = options.orientation.axis;
+ this.options.orientation.axis = options.orientation.axis;
}
}
@@ -131,7 +135,7 @@ TimeAxis.prototype.redraw = function () {
var background = this.dom.background;
// determine the correct parent DOM element (depending on option orientation)
- var parent = (options.orientation == 'top') ? this.body.dom.top : this.body.dom.bottom;
+ var parent = (options.orientation.axis == 'top') ? this.body.dom.top : this.body.dom.bottom;
var parentChanged = (foreground.parentNode !== parent);
// calculate character width and height
@@ -148,7 +152,7 @@ TimeAxis.prototype.redraw = function () {
props.width = foreground.offsetWidth;
props.minorLineHeight = this.body.domProps.root.height - props.majorLabelHeight -
- (options.orientation == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height);
+ (options.orientation.axis == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height);
props.minorLineWidth = 1; // TODO: really calculate width
props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight;
props.majorLineWidth = 1; // TODO: really calculate width
@@ -185,7 +189,7 @@ TimeAxis.prototype.redraw = function () {
* @private
*/
TimeAxis.prototype._repaintLabels = function () {
- var orientation = this.options.orientation;
+ var orientation = this.options.orientation.axis;
// calculate range and step (step such that we have space for 7 characters per label)
var start = util.convert(this.body.range.start, 'Number');
diff --git a/lib/timeline/component/item/BackgroundItem.js b/lib/timeline/component/item/BackgroundItem.js
index dba94be0..cdebc4ed 100644
--- a/lib/timeline/component/item/BackgroundItem.js
+++ b/lib/timeline/component/item/BackgroundItem.js
@@ -143,7 +143,7 @@ BackgroundItem.prototype.repositionX = RangeItem.prototype.repositionX;
* @Override
*/
BackgroundItem.prototype.repositionY = function(margin) {
- var onTop = this.options.orientation === 'top';
+ var onTop = this.options.orientation.item === 'top';
this.dom.content.style.top = onTop ? '' : '0';
this.dom.content.style.bottom = onTop ? '0' : '';
var height;
diff --git a/lib/timeline/component/item/BoxItem.js b/lib/timeline/component/item/BoxItem.js
index 171a573a..65b720a3 100644
--- a/lib/timeline/component/item/BoxItem.js
+++ b/lib/timeline/component/item/BoxItem.js
@@ -191,7 +191,7 @@ BoxItem.prototype.repositionX = function() {
* @Override
*/
BoxItem.prototype.repositionY = function() {
- var orientation = this.options.orientation;
+ var orientation = this.options.orientation.item;
var box = this.dom.box;
var line = this.dom.line;
var dot = this.dom.dot;
diff --git a/lib/timeline/component/item/PointItem.js b/lib/timeline/component/item/PointItem.js
index d70f6a3b..ace98250 100644
--- a/lib/timeline/component/item/PointItem.js
+++ b/lib/timeline/component/item/PointItem.js
@@ -166,8 +166,8 @@ PointItem.prototype.repositionX = function() {
* @Override
*/
PointItem.prototype.repositionY = function() {
- var orientation = this.options.orientation,
- point = this.dom.point;
+ var orientation = this.options.orientation.item;
+ var point = this.dom.point;
if (orientation == 'top') {
point.style.top = this.top + 'px';
diff --git a/lib/timeline/component/item/RangeItem.js b/lib/timeline/component/item/RangeItem.js
index 10e36a78..64b80a97 100644
--- a/lib/timeline/component/item/RangeItem.js
+++ b/lib/timeline/component/item/RangeItem.js
@@ -234,8 +234,8 @@ RangeItem.prototype.repositionX = function(limitSize) {
* @Override
*/
RangeItem.prototype.repositionY = function() {
- var orientation = this.options.orientation,
- box = this.dom.box;
+ var orientation = this.options.orientation.item;
+ var box = this.dom.box;
if (orientation == 'top') {
box.style.top = this.top + 'px';
diff --git a/lib/timeline/options.js b/lib/timeline/options.js
new file mode 100644
index 00000000..750f84c8
--- /dev/null
+++ b/lib/timeline/options.js
@@ -0,0 +1,201 @@
+/**
+ * This object contains all possible options. It will check if the types are correct, if required if the option is one
+ * of the allowed values.
+ *
+ * __any__ means that the name of the property does not matter.
+ * __type__ is a required field for all objects and contains the allowed types of all objects
+ */
+let string = 'string';
+let boolean = 'boolean';
+let number = 'number';
+let array = 'array';
+let date = 'date';
+let object = 'object'; // should only be in a __type__ property
+let dom = 'dom';
+let moment = 'moment';
+let fn = 'function';
+let nada = 'null';
+let undef = 'undefined';
+let any = 'any';
+
+
+let allOptions = {
+ configure: {
+ enabled: {boolean},
+ filter: {boolean,string,array},
+ container: {dom},
+ __type__: {object,boolean,string,array}
+ },
+
+ //globals :
+ align: {string},
+ autoResize: {boolean},
+ clickToUse: {boolean},
+ dataAttributes: {string, array},
+ editable: {boolean, object},
+ end: {number, date, string, moment},
+ format: {
+ minorLabels: {
+ millisecond: {string,undef},
+ second: {string,undef},
+ minute: {string,undef},
+ hour: {string,undef},
+ weekday: {string,undef},
+ day: {string,undef},
+ month: {string,undef},
+ year: {string,undef},
+ __type__: {object}
+ },
+ majorLabels: {
+ millisecond: {string,undef},
+ second: {string,undef},
+ minute: {string,undef},
+ hour: {string,undef},
+ weekday: {string,undef},
+ day: {string,undef},
+ month: {string,undef},
+ year: {string,undef},
+ __type__: {object}
+ },
+ __type__: {object}
+ },
+ groupOrder: {string, fn},
+ height: {string, number},
+ hiddenDates: {object, array},
+ locale:{string},
+ locales:{
+ __any__: {object},
+ __type__: {object}
+ },
+ margin: {
+ axis: {number},
+ item: {
+ horizontal: {number},
+ vertical: {number},
+ __type__: {object,number}
+ },
+ __type__: {object,number}
+ },
+ max: {date, number, string, moment},
+ maxHeight: {number, string},
+ min: {date, number, string, moment},
+ minHeight: {number, string},
+ moveable: {boolean},
+ multiselect: {boolean},
+ onAdd: {fn},
+ onUpdate: {fn},
+ onMove: {fn},
+ onMoving: {fn},
+ onRemove: {fn},
+ order: {fn},
+ orientation: {
+ axis: {string},
+ item: {string},
+ __type__: {string, object}
+ },
+ selectable: {boolean},
+ showCurrentTime: {boolean},
+ showMajorLabels: {boolean},
+ showMinorLabels: {boolean},
+ stack: {boolean},
+ snap: {fn, nada},
+ start: {date, number, string, moment},
+ template: {fn},
+ timeAxis: {
+ scale: {string},
+ step: {number},
+ __type__: {object}
+ },
+ type: {string},
+ width: {string, number},
+ zoomable: {boolean},
+ zoomMax: {number},
+ zoomMin: {number},
+
+ __type__: {object}
+};
+
+let configureOptions = {
+ global: {
+ align: ['center', 'left', 'right'],
+ autoResize: true,
+ clickToUse: false,
+ // dataAttributes: ['all'], // FIXME: can be 'all' or string[]
+ editable: {
+ add: false,
+ remove: false,
+ updateGroup: false,
+ updateTime: false
+ },
+ end: '',
+ format: {
+ minorLabels: {
+ millisecond:'SSS',
+ second: 's',
+ minute: 'HH:mm',
+ hour: 'HH:mm',
+ weekday: 'ddd D',
+ day: 'D',
+ month: 'MMM',
+ year: 'YYYY'
+ },
+ majorLabels: {
+ millisecond:'HH:mm:ss',
+ second: 'D MMMM HH:mm',
+ minute: 'ddd D MMMM',
+ hour: 'ddd D MMMM',
+ weekday: 'MMMM YYYY',
+ day: 'MMMM YYYY',
+ month: 'YYYY',
+ year: ''
+ }
+ },
+
+ //groupOrder: {string, fn},
+ height: '',
+ //hiddenDates: {object, array},
+ locale: '',
+ margin: {
+ axis: [20, 0, 100, 1],
+ item: {
+ horizontal: [10, 0, 100, 1],
+ vertical: [10, 0, 100, 1]
+ }
+ },
+ max: '',
+ maxHeight: '',
+ min: '',
+ minHeight: '',
+ moveable: false,
+ multiselect: false,
+ //onAdd: {fn},
+ //onUpdate: {fn},
+ //onMove: {fn},
+ //onMoving: {fn},
+ //onRename: {fn},
+ //order: {fn},
+ orientation: {
+ axis: ['both', 'bottom', 'top'],
+ item: ['bottom', 'top']
+ },
+ selectable: true,
+ showCurrentTime: false,
+ showMajorLabels: true,
+ showMinorLabels: true,
+ stack: true,
+ //snap: {fn, nada},
+ start: '',
+ //template: {fn},
+ //timeAxis: {
+ // scale: ['millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'month', 'year'],
+ // step: [1, 1, 10, 1]
+ //},
+ type: ['box', 'point', 'range', 'background'],
+ width: '100%',
+ zoomable: true,
+ zoomMax: [315360000000000, 10, 315360000000000, 1],
+ zoomMin: [10, 10, 315360000000000, 1]
+ }
+};
+
+export {allOptions, configureOptions};
\ No newline at end of file
diff --git a/test/timeline.html b/test/timeline.html
index bc6769cb..7923cb33 100644
--- a/test/timeline.html
+++ b/test/timeline.html
@@ -142,6 +142,8 @@
var container = document.getElementById('visualization');
var options = {
+ configure: true,
+ multiselect: true,
editable: true,
//orientation: 'top',
orientation: 'both',
diff --git a/test/timeline_groups.html b/test/timeline_groups.html
index bef4ac9a..c6592c54 100644
--- a/test/timeline_groups.html
+++ b/test/timeline_groups.html
@@ -90,6 +90,7 @@
var container = document.getElementById('visualization');
var options = {
//orientation: 'top',
+ multiselect: true,
editable: {
add: true,
remove: true,