diff --git a/dist/vis.js b/dist/vis.js
index 525827b2..096b66d7 100644
--- a/dist/vis.js
+++ b/dist/vis.js
@@ -5,7 +5,7 @@
* A dynamic, browser-based visualization library.
*
* @version 3.5.0
- * @date 2014-09-22
+ * @date 2014-09-23
*
* @license
* Copyright (C) 2011-2014 Almende B.V, http://almende.com
@@ -102,7 +102,7 @@ return /******/ (function(modules) { // webpackBootstrap
exports.Timeline = __webpack_require__(17);
exports.Graph2d = __webpack_require__(39);
exports.timeline = {
- DataStep: __webpack_require__(42),
+ DataStep: __webpack_require__(40),
Range: __webpack_require__(20),
stack: __webpack_require__(31),
TimeStep: __webpack_require__(25),
@@ -120,11 +120,11 @@ return /******/ (function(modules) { // webpackBootstrap
CurrentTime: __webpack_require__(26),
CustomTime: __webpack_require__(28),
DataAxis: __webpack_require__(41),
- GraphGroup: __webpack_require__(43),
+ GraphGroup: __webpack_require__(42),
Group: __webpack_require__(30),
ItemSet: __webpack_require__(29),
- Legend: __webpack_require__(44),
- LineGraph: __webpack_require__(40),
+ Legend: __webpack_require__(43),
+ LineGraph: __webpack_require__(44),
TimeAxis: __webpack_require__(24)
}
};
@@ -9255,11 +9255,18 @@ return /******/ (function(modules) { // webpackBootstrap
* @constructor
* @extends Core
*/
- function Timeline (container, items, options) {
+ function Timeline (container, items, groups, options) {
if (!(this instanceof Timeline)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
+ // if the third element is options, the forth is groups (optionally);
+ if (!(groups instanceof Array || groups instanceof vis.DataSet) && groups instanceof Object) {
+ var forthArgument = options;
+ options = groups;
+ groups = forthArgument;
+ }
+
var me = this;
this.defaultOptions = {
start: null,
@@ -9329,6 +9336,11 @@ return /******/ (function(modules) { // webpackBootstrap
this.setOptions(options);
}
+ // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
+ if (groups) {
+ this.setGroups(groups);
+ }
+
// create itemset
if (items) {
this.setItems(items);
@@ -18555,7 +18567,7 @@ return /******/ (function(modules) { // webpackBootstrap
var TimeAxis = __webpack_require__(24);
var CurrentTime = __webpack_require__(26);
var CustomTime = __webpack_require__(28);
- var LineGraph = __webpack_require__(40);
+ var LineGraph = __webpack_require__(44);
/**
* Create a timeline visualization
@@ -18565,7 +18577,14 @@ return /******/ (function(modules) { // webpackBootstrap
* @constructor
* @extends Core
*/
- function Graph2d (container, items, options, groups) {
+ function Graph2d (container, items, groups, options) {
+ // if the third element is options, the forth is groups (optionally);
+ if (!(groups instanceof Array || groups instanceof vis.DataSet) && groups instanceof Object) {
+ var forthArgument = options;
+ options = groups;
+ groups = forthArgument;
+ }
+
var me = this;
this.defaultOptions = {
start: null,
@@ -18784,2386 +18803,2386 @@ return /******/ (function(modules) { // webpackBootstrap
/* 40 */
/***/ function(module, exports, __webpack_require__) {
- var util = __webpack_require__(1);
- var DOMutil = __webpack_require__(6);
- var DataSet = __webpack_require__(7);
- var DataView = __webpack_require__(8);
- var Component = __webpack_require__(22);
- var DataAxis = __webpack_require__(41);
- var GraphGroup = __webpack_require__(43);
- var Legend = __webpack_require__(44);
-
- var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
-
/**
- * This is the constructor of the LineGraph. It requires a Timeline body and options.
+ * @constructor DataStep
+ * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
+ * end data point. The class itself determines the best scale (step size) based on the
+ * provided start Date, end Date, and minimumStep.
*
- * @param body
- * @param options
- * @constructor
+ * If minimumStep is provided, the step size is chosen as close as possible
+ * to the minimumStep but larger than minimumStep. If minimumStep is not
+ * provided, the scale is set to 1 DAY.
+ * The minimumStep should correspond with the onscreen size of about 6 characters
+ *
+ * Alternatively, you can set a scale by hand.
+ * After creation, you can initialize the class by executing first(). Then you
+ * can iterate from the start date to the end date via next(). You can check if
+ * the end date is reached with the function hasNext(). After each step, you can
+ * retrieve the current date via getCurrent().
+ * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
+ * days, to years.
+ *
+ * Version: 1.2
+ *
+ * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
+ * or new Date(2010, 9, 21, 23, 45, 00)
+ * @param {Date} [end] The end date
+ * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
*/
- function LineGraph(body, options) {
- this.id = util.randomUUID();
- this.body = body;
-
- this.defaultOptions = {
- yAxisOrientation: 'left',
- defaultGroup: 'default',
- sort: true,
- sampling: true,
- graphHeight: '400px',
- shaded: {
- enabled: false,
- orientation: 'bottom' // top, bottom
- },
- style: 'line', // line, bar
- barChart: {
- width: 50,
- handleOverlap: 'overlap',
- align: 'center' // left, center, right
- },
- catmullRom: {
- enabled: true,
- parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5)
- alpha: 0.5
- },
- drawPoints: {
- enabled: true,
- size: 6,
- style: 'square' // square, circle
- },
- dataAxis: {
- showMinorLabels: true,
- showMajorLabels: true,
- icons: false,
- width: '40px',
- visible: true,
- customRange: {
- left: {min:undefined, max:undefined},
- right: {min:undefined, max:undefined}
- }
- },
- legend: {
- enabled: false,
- icons: true,
- left: {
- visible: true,
- position: 'top-left' // top/bottom - left,right
- },
- right: {
- visible: true,
- position: 'top-right' // top/bottom - left,right
- }
- },
- groups: {
- visibility: {}
- }
- };
+ function DataStep(start, end, minimumStep, containerHeight, customRange) {
+ // variables
+ this.current = 0;
- // options is shared by this ItemSet and all its items
- this.options = util.extend({}, this.defaultOptions);
- this.dom = {};
- this.props = {};
- this.hammer = null;
- this.groups = {};
- this.abortedGraphUpdate = false;
+ this.autoScale = true;
+ this.stepIndex = 0;
+ this.step = 1;
+ this.scale = 1;
- var me = this;
- this.itemsData = null; // DataSet
- this.groupsData = null; // DataSet
+ this.marginStart;
+ this.marginEnd;
+ this.deadSpace = 0;
- // listeners for the DataSet of the items
- this.itemListeners = {
- 'add': function (event, params, senderId) {
- me._onAdd(params.items);
- },
- 'update': function (event, params, senderId) {
- me._onUpdate(params.items);
- },
- 'remove': function (event, params, senderId) {
- me._onRemove(params.items);
- }
- };
+ this.majorSteps = [1, 2, 5, 10];
+ this.minorSteps = [0.25, 0.5, 1, 2];
- // 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.setRange(start, end, minimumStep, containerHeight, customRange);
+ }
- 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;
- this.touchParams = {}; // stores properties while dragging
- this.svgElements = {};
- this.setOptions(options);
- this.groupsUsingDefaultStyles = [0];
- this.body.emitter.on("rangechanged", function() {
- me.lastStart = me.body.range.start;
- me.svg.style.left = util.option.asSize(-me.width);
- me._updateGraph.apply(me);
- });
+ /**
+ * Set a new range
+ * If minimumStep is provided, the step size is chosen as close as possible
+ * to the minimumStep but larger than minimumStep. If minimumStep is not
+ * provided, the scale is set to 1 DAY.
+ * The minimumStep should correspond with the onscreen size of about 6 characters
+ * @param {Number} [start] The start date and time.
+ * @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, customRange) {
+ this._start = customRange.min === undefined ? start : customRange.min;
+ this._end = customRange.max === undefined ? end : customRange.max;
- // create the HTML DOM
- this._create();
- this.body.emitter.emit("change");
- }
+ if (this._start == this._end) {
+ this._start -= 0.75;
+ this._end += 1;
+ }
- LineGraph.prototype = new Component();
+ if (this.autoScale) {
+ this.setMinimumStep(minimumStep, containerHeight);
+ }
+ this.setFirst(customRange);
+ };
/**
- * Create the HTML DOM for the ItemSet
+ * Automatically determine the scale that bests fits the provided minimum step
+ * @param {Number} [minimumStep] The minimum step size in milliseconds
*/
- LineGraph.prototype._create = function(){
- var frame = document.createElement('div');
- frame.className = 'LineGraph';
- this.dom.frame = frame;
+ DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
+ // round to floor
+ var size = this._end - this._start;
+ var safeSize = size * 1.2;
+ var minimumStepValue = minimumStep * (safeSize / containerHeight);
+ var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);
- // create svg element for graph drawing.
- this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
- this.svg.style.position = "relative";
- this.svg.style.height = ('' + this.options.graphHeight).replace("px",'') + 'px';
- this.svg.style.display = "block";
- frame.appendChild(this.svg);
+ var minorStepIdx = -1;
+ var magnitudefactor = Math.pow(10,orderOfMagnitude);
- // data axis
- this.options.dataAxis.orientation = 'left';
- this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
+ var start = 0;
+ if (orderOfMagnitude < 0) {
+ start = orderOfMagnitude;
+ }
- this.options.dataAxis.orientation = 'right';
- this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
- delete this.options.dataAxis.orientation;
+ var solutionFound = false;
+ for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
+ magnitudefactor = Math.pow(10,i);
+ for (var j = 0; j < this.minorSteps.length; j++) {
+ var stepSize = magnitudefactor * this.minorSteps[j];
+ if (stepSize >= minimumStepValue) {
+ solutionFound = true;
+ minorStepIdx = j;
+ break;
+ }
+ }
+ if (solutionFound == true) {
+ break;
+ }
+ }
+ this.stepIndex = minorStepIdx;
+ this.scale = magnitudefactor;
+ this.step = magnitudefactor * this.minorSteps[minorStepIdx];
+ };
- // legends
- this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups);
- this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups);
- this.show();
- };
/**
- * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
- * @param options
+ * Round the current date to the first minor date value
+ * This must be executed once when the current date is set to start Date
*/
- LineGraph.prototype.setOptions = function(options) {
- if (options) {
- var fields = ['sampling','defaultGroup','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort','groups'];
- util.selectiveDeepExtend(fields, this.options, options);
- util.mergeOptions(this.options, options,'catmullRom');
- util.mergeOptions(this.options, options,'drawPoints');
- util.mergeOptions(this.options, options,'shaded');
- util.mergeOptions(this.options, options,'legend');
+ DataStep.prototype.setFirst = function(customRange) {
+ if (customRange === undefined) {
+ customRange = {};
+ }
+ var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min;
+ var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max;
- if (options.catmullRom) {
- if (typeof options.catmullRom == 'object') {
- if (options.catmullRom.parametrization) {
- if (options.catmullRom.parametrization == 'uniform') {
- this.options.catmullRom.alpha = 0;
- }
- else if (options.catmullRom.parametrization == 'chordal') {
- this.options.catmullRom.alpha = 1.0;
- }
- else {
- this.options.catmullRom.parametrization = 'centripetal';
- this.options.catmullRom.alpha = 0.5;
- }
- }
- }
- }
+ this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max;
+ this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min;
+ this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart;
+ this.marginRange = this.marginEnd - this.marginStart;
- if (this.yAxisLeft) {
- if (options.dataAxis !== undefined) {
- this.yAxisLeft.setOptions(this.options.dataAxis);
- this.yAxisRight.setOptions(this.options.dataAxis);
- }
- }
+ this.current = this.marginEnd;
- if (this.legendLeft) {
- if (options.legend !== undefined) {
- this.legendLeft.setOptions(this.options.legend);
- this.legendRight.setOptions(this.options.legend);
- }
- }
+ };
- if (this.groups.hasOwnProperty(UNGROUPED)) {
- this.groups[UNGROUPED].setOptions(options);
- }
+ DataStep.prototype.roundToMinor = function(value) {
+ var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
+ if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
+ return rounded + (this.scale * this.minorSteps[this.stepIndex]);
}
- if (this.dom.frame) {
- this._updateGraph();
+ else {
+ return rounded;
}
- };
+ }
+
/**
- * Hide the component from the DOM
+ * Check if the there is a next step
+ * @return {boolean} true if the current date has not passed the end date
*/
- LineGraph.prototype.hide = function() {
- // remove the frame containing the items
- if (this.dom.frame.parentNode) {
- this.dom.frame.parentNode.removeChild(this.dom.frame);
- }
+ DataStep.prototype.hasNext = function () {
+ return (this.current >= this.marginStart);
};
/**
- * Show the component in the DOM (when not already visible).
- * @return {Boolean} changed
+ * Do the next step
*/
- LineGraph.prototype.show = function() {
- // show frame containing the items
- if (!this.dom.frame.parentNode) {
- this.body.dom.center.appendChild(this.dom.frame);
+ DataStep.prototype.next = function() {
+ var prev = this.current;
+ this.current -= this.step;
+
+ // safety mechanism: if current time is still unchanged, move to the end
+ if (this.current == prev) {
+ this.current = this._end;
}
};
-
/**
- * Set items
- * @param {vis.DataSet | null} items
+ * Do the next step
*/
- LineGraph.prototype.setItems = function(items) {
- var me = this,
- ids,
- oldItemsData = this.itemsData;
-
- // replace the dataset
- if (!items) {
- this.itemsData = null;
- }
- else if (items instanceof DataSet || items instanceof DataView) {
- this.itemsData = items;
- }
- else {
- throw new TypeError('Data must be an instance of DataSet or DataView');
- }
-
- if (oldItemsData) {
- // unsubscribe from old dataset
- util.forEach(this.itemListeners, function (callback, event) {
- oldItemsData.off(event, callback);
- });
-
- // remove all drawn items
- ids = oldItemsData.getIds();
- this._onRemove(ids);
- }
+ DataStep.prototype.previous = function() {
+ this.current += this.step;
+ this.marginEnd += this.step;
+ this.marginRange = this.marginEnd - this.marginStart;
+ };
- if (this.itemsData) {
- // subscribe to new dataset
- var id = this.id;
- util.forEach(this.itemListeners, function (callback, event) {
- me.itemsData.on(event, callback, id);
- });
- // add all new items
- ids = this.itemsData.getIds();
- this._onAdd(ids);
- }
- this._updateUngrouped();
- this._updateGraph();
- this.redraw();
- };
/**
- * Set groups
- * @param {vis.DataSet} groups
+ * Get the current datetime
+ * @return {String} current The current date
*/
- LineGraph.prototype.setGroups = function(groups) {
- var me = this,
- ids;
+ DataStep.prototype.getCurrent = function() {
+ var toPrecision = '' + Number(this.current).toPrecision(5);
+ if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) {
+ for (var i = toPrecision.length-1; i > 0; i--) {
+ if (toPrecision[i] == "0") {
+ toPrecision = toPrecision.slice(0,i);
+ }
+ else if (toPrecision[i] == "." || toPrecision[i] == ",") {
+ toPrecision = toPrecision.slice(0,i);
+ break;
+ }
+ else{
+ break;
+ }
+ }
+ }
- // unsubscribe from current dataset
- if (this.groupsData) {
- util.forEach(this.groupListeners, function (callback, event) {
- me.groupsData.unsubscribe(event, callback);
- });
+ return toPrecision;
+ };
- // 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);
- });
+ /**
+ * Snap a date to a rounded value.
+ * The snap intervals are dependent on the current scale and step.
+ * @param {Date} date the date to be snapped.
+ * @return {Date} snappedDate
+ */
+ DataStep.prototype.snap = function(date) {
- // draw all ms
- ids = this.groupsData.getIds();
- this._onAddGroups(ids);
- }
- this._onUpdate();
};
-
/**
- * Update the datapoints
- * @param [ids]
- * @private
+ * Check if the current value is a major value (for example when the step
+ * is DAY, a major value is each first day of the MONTH)
+ * @return {boolean} true if current date is major, else false.
*/
- LineGraph.prototype._onUpdate = function(ids) {
- this._updateUngrouped();
- this._updateAllGroupData();
- this._updateGraph();
- this.redraw();
+ DataStep.prototype.isMajor = function() {
+ return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
};
- LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
- LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
- LineGraph.prototype._onUpdateGroups = function (groupIds) {
- for (var i = 0; i < groupIds.length; i++) {
- var group = this.groupsData.get(groupIds[i]);
- this._updateGroup(group, groupIds[i]);
- }
- this._updateGraph();
- this.redraw();
- };
- LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
+ module.exports = DataStep;
- LineGraph.prototype._onRemoveGroups = function (groupIds) {
- for (var i = 0; i < groupIds.length; i++) {
- if (!this.groups.hasOwnProperty(groupIds[i])) {
- if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
- this.yAxisRight.removeGroup(groupIds[i]);
- this.legendRight.removeGroup(groupIds[i]);
- this.legendRight.redraw();
- }
- else {
- this.yAxisLeft.removeGroup(groupIds[i]);
- this.legendLeft.removeGroup(groupIds[i]);
- this.legendLeft.redraw();
- }
- delete this.groups[groupIds[i]];
- }
- }
- this._updateUngrouped();
- this._updateGraph();
- this.redraw();
- };
+
+/***/ },
+/* 41 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var util = __webpack_require__(1);
+ var DOMutil = __webpack_require__(6);
+ var Component = __webpack_require__(22);
+ var DataStep = __webpack_require__(40);
/**
- * update a group object
- *
- * @param group
- * @param groupId
- * @private
+ * A horizontal time axis
+ * @param {Object} [options] See DataAxis.setOptions for the available
+ * options.
+ * @constructor DataAxis
+ * @extends Component
+ * @param body
*/
- LineGraph.prototype._updateGroup = function (group, groupId) {
- if (!this.groups.hasOwnProperty(groupId)) {
- this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles);
- if (this.groups[groupId].options.yAxisOrientation == 'right') {
- this.yAxisRight.addGroup(groupId, this.groups[groupId]);
- this.legendRight.addGroup(groupId, this.groups[groupId]);
- }
- else {
- this.yAxisLeft.addGroup(groupId, this.groups[groupId]);
- this.legendLeft.addGroup(groupId, this.groups[groupId]);
- }
- }
- else {
- this.groups[groupId].update(group);
- if (this.groups[groupId].options.yAxisOrientation == 'right') {
- this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
- this.legendRight.updateGroup(groupId, this.groups[groupId]);
- }
- else {
- this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
- this.legendLeft.updateGroup(groupId, this.groups[groupId]);
+ function DataAxis (body, options, svg, linegraphOptions) {
+ this.id = util.randomUUID();
+ this.body = body;
+
+ this.defaultOptions = {
+ orientation: 'left', // supported: 'left', 'right'
+ showMinorLabels: true,
+ showMajorLabels: true,
+ icons: true,
+ majorLinesOffset: 7,
+ minorLinesOffset: 4,
+ labelOffsetX: 10,
+ labelOffsetY: 2,
+ iconWidth: 20,
+ width: '40px',
+ visible: true,
+ customRange: {
+ left: {min:undefined, max:undefined},
+ right: {min:undefined, max:undefined}
}
+ };
+
+ this.linegraphOptions = linegraphOptions;
+ this.linegraphSVG = svg;
+ this.props = {};
+ this.DOMelements = { // dynamic elements
+ lines: {},
+ labels: {}
+ };
+
+ this.dom = {};
+
+ this.range = {start:0, end:0};
+
+ this.options = util.extend({}, this.defaultOptions);
+ this.conversionFactor = 1;
+
+ this.setOptions(options);
+ this.width = Number(('' + this.options.width).replace("px",""));
+ this.minWidth = this.width;
+ this.height = this.linegraphSVG.offsetHeight;
+
+ this.stepPixels = 25;
+ this.stepPixelsForced = 25;
+ this.lineOffset = 0;
+ this.master = true;
+ this.svgElements = {};
+
+
+ this.groups = {};
+ this.amountOfGroups = 0;
+
+ // create the HTML DOM
+ this._create();
+ }
+
+ DataAxis.prototype = new Component();
+
+
+
+ DataAxis.prototype.addGroup = function(label, graphOptions) {
+ if (!this.groups.hasOwnProperty(label)) {
+ this.groups[label] = graphOptions;
}
- this.legendLeft.redraw();
- this.legendRight.redraw();
+ this.amountOfGroups += 1;
};
- LineGraph.prototype._updateAllGroupData = function () {
- if (this.itemsData != null) {
- var groupsContent = {};
- var groupId;
- for (groupId in this.groups) {
- if (this.groups.hasOwnProperty(groupId)) {
- groupsContent[groupId] = [];
- }
- }
- for (var itemId in this.itemsData._data) {
- if (this.itemsData._data.hasOwnProperty(itemId)) {
- var item = this.itemsData._data[itemId];
- item.x = util.convert(item.x,"Date");
- groupsContent[item.group].push(item);
- }
- }
- for (groupId in this.groups) {
- if (this.groups.hasOwnProperty(groupId)) {
- this.groups[groupId].setItems(groupsContent[groupId]);
- }
- }
+ DataAxis.prototype.updateGroup = function(label, graphOptions) {
+ this.groups[label] = graphOptions;
+ };
+
+ DataAxis.prototype.removeGroup = function(label) {
+ if (this.groups.hasOwnProperty(label)) {
+ delete this.groups[label];
+ this.amountOfGroups -= 1;
}
};
- /**
- * Create or delete the group holding all ungrouped items. This group is used when
- * there are no groups specified. This anonymous group is called 'graph'.
- * @protected
- */
- LineGraph.prototype._updateUngrouped = function() {
- if (this.itemsData && this.itemsData != null) {
- var ungroupedCounter = 0;
- for (var itemId in this.itemsData._data) {
- if (this.itemsData._data.hasOwnProperty(itemId)) {
- var item = this.itemsData._data[itemId];
- if (item != undefined) {
- if (item.hasOwnProperty('group')) {
- if (item.group === undefined) {
- item.group = UNGROUPED;
- }
- }
- else {
- item.group = UNGROUPED;
- }
- ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter;
- }
- }
- }
- if (ungroupedCounter == 0) {
- delete this.groups[UNGROUPED];
- this.legendLeft.removeGroup(UNGROUPED);
- this.legendRight.removeGroup(UNGROUPED);
- this.yAxisLeft.removeGroup(UNGROUPED);
- this.yAxisRight.removeGroup(UNGROUPED);
+ DataAxis.prototype.setOptions = function (options) {
+ if (options) {
+ var redraw = false;
+ if (this.options.orientation != options.orientation && options.orientation !== undefined) {
+ redraw = true;
}
- else {
- var group = {id: UNGROUPED, content: this.options.defaultGroup};
- this._updateGroup(group, UNGROUPED);
+ var fields = [
+ 'orientation',
+ 'showMinorLabels',
+ 'showMajorLabels',
+ 'icons',
+ 'majorLinesOffset',
+ 'minorLinesOffset',
+ 'labelOffsetX',
+ 'labelOffsetY',
+ 'iconWidth',
+ 'width',
+ 'visible',
+ 'customRange'
+ ];
+ util.selectiveExtend(fields, this.options, options);
+
+ this.minWidth = Number(('' + this.options.width).replace("px",""));
+
+ if (redraw == true && this.dom.frame) {
+ this.hide();
+ this.show();
}
}
- else {
- delete this.groups[UNGROUPED];
- this.legendLeft.removeGroup(UNGROUPED);
- this.legendRight.removeGroup(UNGROUPED);
- this.yAxisLeft.removeGroup(UNGROUPED);
- this.yAxisRight.removeGroup(UNGROUPED);
- }
-
- this.legendLeft.redraw();
- this.legendRight.redraw();
};
/**
- * Redraw the component, mandatory function
- * @return {boolean} Returns true if the component is resized
+ * Create the HTML DOM for the DataAxis
*/
- LineGraph.prototype.redraw = function() {
- var resized = false;
+ DataAxis.prototype._create = function() {
+ this.dom.frame = document.createElement('div');
+ this.dom.frame.style.width = this.options.width;
+ this.dom.frame.style.height = this.height;
- this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
- if (this.lastWidth === undefined && this.width || this.lastWidth != this.width) {
- resized = true;
- }
- // check if this component is resized
- resized = this._isResized() || resized;
- // check whether zoomed (in that case we need to re-stack everything)
- var visibleInterval = this.body.range.end - this.body.range.start;
- var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.width != this.lastWidth);
- this.lastVisibleInterval = visibleInterval;
- this.lastWidth = this.width;
+ this.dom.lineContainer = document.createElement('div');
+ this.dom.lineContainer.style.width = '100%';
+ this.dom.lineContainer.style.height = this.height;
- // calculate actual size and position
- this.width = this.dom.frame.offsetWidth;
+ // create svg element for graph drawing.
+ this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
+ this.svg.style.position = "absolute";
+ this.svg.style.top = '0px';
+ this.svg.style.height = '100%';
+ this.svg.style.width = '100%';
+ this.svg.style.display = "block";
+ this.dom.frame.appendChild(this.svg);
+ };
- // the svg element is three times as big as the width, this allows for fully dragging left and right
- // without reloading the graph. the controls for this are bound to events in the constructor
- if (resized == true) {
- this.svg.style.width = util.option.asSize(3*this.width);
- this.svg.style.left = util.option.asSize(-this.width);
- }
+ DataAxis.prototype._redrawGroupIcons = function () {
+ DOMutil.prepareElements(this.svgElements);
- if (zoomed == true || this.abortedGraphUpdate == true) {
- this._updateGraph();
+ var x;
+ var iconWidth = this.options.iconWidth;
+ var iconHeight = 15;
+ var iconOffset = 4;
+ var y = iconOffset + 0.5 * iconHeight;
+
+ if (this.options.orientation == 'left') {
+ x = iconOffset;
}
else {
- // move the whole svg while dragging
- if (this.lastStart != 0) {
- var offset = this.body.range.start - this.lastStart;
- var range = this.body.range.end - this.body.range.start;
- if (this.width != 0) {
- var rangePerPixelInv = this.width/range;
- var xOffset = offset * rangePerPixelInv;
- this.svg.style.left = (-this.width - xOffset) + "px";
+ x = this.width - iconWidth - iconOffset;
+ }
+
+ for (var groupId in this.groups) {
+ if (this.groups.hasOwnProperty(groupId)) {
+ if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
+ this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
+ y += iconHeight + iconOffset;
}
}
-
}
- this.legendLeft.redraw();
- this.legendRight.redraw();
-
- return resized;
- };
+ DOMutil.cleanupElements(this.svgElements);
+ };
/**
- * Update and redraw the graph.
- *
+ * Create the HTML DOM for the DataAxis
*/
- LineGraph.prototype._updateGraph = function () {
- // reset the svg elements
- DOMutil.prepareElements(this.svgElements);
- if (this.width != 0 && this.itemsData != null) {
- var group, i;
- var preprocessedGroupData = {};
- var processedGroupData = {};
- var groupRanges = {};
- var changeCalled = false;
-
- // getting group Ids
- var groupIds = [];
- for (var groupId in this.groups) {
- if (this.groups.hasOwnProperty(groupId)) {
- group = this.groups[groupId];
- if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) {
- groupIds.push(groupId);
- }
- }
+ DataAxis.prototype.show = function() {
+ if (!this.dom.frame.parentNode) {
+ if (this.options.orientation == 'left') {
+ this.body.dom.left.appendChild(this.dom.frame);
}
- if (groupIds.length > 0) {
- // this is the range of the SVG canvas
- var minDate = this.body.util.toGlobalTime(- this.body.domProps.root.width);
- var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
- var groupsData = {};
- // fill groups data
- this._getRelevantData(groupIds, groupsData, minDate, maxDate);
- // we transform the X coordinates to detect collisions
- for (i = 0; i < groupIds.length; i++) {
- preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]);
- }
- // now all needed data has been collected we start the processing.
- this._getYRanges(groupIds, preprocessedGroupData, groupRanges);
-
- // update the Y axis first, we use this data to draw at the correct Y points
- // changeCalled is required to clean the SVG on a change emit.
- changeCalled = this._updateYAxis(groupIds, groupRanges);
- if (changeCalled == true) {
- DOMutil.cleanupElements(this.svgElements);
- this.abortedGraphUpdate = true;
- this.body.emitter.emit("change");
- return;
- }
- this.abortedGraphUpdate = false;
-
- // With the yAxis scaled correctly, use this to get the Y values of the points.
- for (i = 0; i < groupIds.length; i++) {
- group = this.groups[groupIds[i]];
- processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
- }
+ else {
+ this.body.dom.right.appendChild(this.dom.frame);
+ }
+ }
+ if (!this.dom.lineContainer.parentNode) {
+ this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
+ }
+ };
- // draw the groups
- for (i = 0; i < groupIds.length; i++) {
- group = this.groups[groupIds[i]];
- if (group.options.style == 'line') {
- this._drawLineGraph(processedGroupData[groupIds[i]], group);
- }
- }
- this._drawBarGraphs(groupIds, processedGroupData);
- }
+ /**
+ * Create the HTML DOM for the DataAxis
+ */
+ DataAxis.prototype.hide = function() {
+ if (this.dom.frame.parentNode) {
+ this.dom.frame.parentNode.removeChild(this.dom.frame);
}
- // cleanup unused svg elements
- DOMutil.cleanupElements(this.svgElements);
+ if (this.dom.lineContainer.parentNode) {
+ this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
+ }
};
+ /**
+ * Set a range (start and end)
+ * @param end
+ * @param start
+ * @param end
+ */
+ DataAxis.prototype.setRange = function (start, end) {
+ this.range.start = start;
+ this.range.end = end;
+ };
- LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) {
- // first select and preprocess the data from the datasets.
- // the groups have their preselection of data, we now loop over this data to see
- // what data we need to draw. Sorted data is much faster.
- // more optimization is possible by doing the sampling before and using the binary search
- // to find the end date to determine the increment.
- var group, i, j, item;
- if (groupIds.length > 0) {
- for (i = 0; i < groupIds.length; i++) {
- group = this.groups[groupIds[i]];
- groupsData[groupIds[i]] = [];
- var dataContainer = groupsData[groupIds[i]];
- // optimization for sorted data
- if (group.options.sort == true) {
- var guess = Math.max(0, util.binarySearchGeneric(group.itemsData, minDate, 'x', 'before'));
- for (j = guess; j < group.itemsData.length; j++) {
- item = group.itemsData[j];
- if (item !== undefined) {
- if (item.x > maxDate) {
- dataContainer.push(item);
- break;
- }
- else {
- dataContainer.push(item);
- }
- }
- }
- }
- else {
- for (j = 0; j < group.itemsData.length; j++) {
- item = group.itemsData[j];
- if (item !== undefined) {
- if (item.x > minDate && item.x < maxDate) {
- dataContainer.push(item);
- }
- }
- }
+ /**
+ * Repaint the component
+ * @return {boolean} Returns true if the component is resized
+ */
+ DataAxis.prototype.redraw = function () {
+ var changeCalled = false;
+ var activeGroups = 0;
+ for (var groupId in this.groups) {
+ if (this.groups.hasOwnProperty(groupId)) {
+ if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
+ activeGroups++;
}
}
}
+ if (this.amountOfGroups == 0 || activeGroups == 0) {
+ this.hide();
+ }
+ else {
+ this.show();
+ this.height = Number(this.linegraphSVG.style.height.replace("px",""));
+ // svg offsetheight did not work in firefox and explorer...
- this._applySampling(groupIds, groupsData);
- };
+ this.dom.lineContainer.style.height = this.height + 'px';
+ this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
- LineGraph.prototype._applySampling = function (groupIds, groupsData) {
- var group;
- if (groupIds.length > 0) {
- for (var i = 0; i < groupIds.length; i++) {
- group = this.groups[groupIds[i]];
- if (group.options.sampling == true) {
- var dataContainer = groupsData[groupIds[i]];
- if (dataContainer.length > 0) {
- var increment = 1;
- var amountOfPoints = dataContainer.length;
+ var props = this.props;
+ var frame = this.dom.frame;
- // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
- // of width changing of the yAxis.
- var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x);
- var pointsPerPixel = amountOfPoints / xDistance;
- increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));
+ // update classname
+ frame.className = 'dataaxis';
- var sampledData = [];
- for (var j = 0; j < amountOfPoints; j += increment) {
- sampledData.push(dataContainer[j]);
+ // calculate character width and height
+ this._calculateCharSize();
- }
- groupsData[groupIds[i]] = sampledData;
- }
- }
+ var orientation = this.options.orientation;
+ var showMinorLabels = this.options.showMinorLabels;
+ var showMajorLabels = this.options.showMajorLabels;
+
+ // determine the width and height of the elemens for the axis
+ props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
+ props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
+
+ props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
+ props.minorLineHeight = 1;
+ props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
+ props.majorLineHeight = 1;
+
+ // take frame offline while updating (is almost twice as fast)
+ if (orientation == 'left') {
+ frame.style.top = '0';
+ frame.style.left = '0';
+ frame.style.bottom = '';
+ frame.style.width = this.width + 'px';
+ frame.style.height = this.height + "px";
+ }
+ else { // right
+ frame.style.top = '';
+ frame.style.bottom = '0';
+ frame.style.left = '0';
+ frame.style.width = this.width + 'px';
+ frame.style.height = this.height + "px";
+ }
+ changeCalled = this._redrawLabels();
+ if (this.options.icons == true) {
+ this._redrawGroupIcons();
}
}
+ return changeCalled;
};
- LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
- var groupData, group, i,j;
- var barCombinedDataLeft = [];
- var barCombinedDataRight = [];
- var barCombinedData;
- if (groupIds.length > 0) {
- for (i = 0; i < groupIds.length; i++) {
- groupData = groupsData[groupIds[i]];
- if (groupData.length > 0) {
- group = this.groups[groupIds[i]];
- if (group.options.style == 'line' || group.options.barChart.handleOverlap != "stack") {
- var yMin = groupData[0].y;
- var yMax = groupData[0].y;
- for (j = 0; j < groupData.length; j++) {
- yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
- yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
- }
- groupRanges[groupIds[i]] = {min: yMin, max: yMax, yAxisOrientation: group.options.yAxisOrientation};
- }
- else if (group.options.style == 'bar') {
- if (group.options.yAxisOrientation == 'left') {
- barCombinedData = barCombinedDataLeft;
- }
- else {
- barCombinedData = barCombinedDataRight;
- }
+ /**
+ * Repaint major and minor text labels and vertical grid lines
+ * @private
+ */
+ DataAxis.prototype._redrawLabels = function () {
+ DOMutil.prepareElements(this.DOMelements.lines);
+ DOMutil.prepareElements(this.DOMelements.labels);
- groupRanges[groupIds[i]] = {min: 0, max: 0, yAxisOrientation: group.options.yAxisOrientation, ignore: true};
+ var orientation = this.options['orientation'];
- // combine data
- for (j = 0; j < groupData.length; j++) {
- barCombinedData.push({
- x: groupData[j].x,
- y: groupData[j].y,
- groupId: groupIds[i]
- });
- }
- }
- }
- }
+ // calculate range and step (step such that we have space for 7 characters per label)
+ var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
- var intersections;
- if (barCombinedDataLeft.length > 0) {
- // sort by time and by group
- barCombinedDataLeft.sort(function (a, b) {
- if (a.x == b.x) {
- return a.groupId - b.groupId;
- } else {
- return a.x - b.x;
- }
- });
- intersections = {};
- this._getDataIntersections(intersections, barCombinedDataLeft);
- groupRanges["__barchartLeft"] = this._getStackedBarYRange(intersections, barCombinedDataLeft);
- groupRanges["__barchartLeft"].yAxisOrientation = "left";
- groupIds.push("__barchartLeft");
- }
- if (barCombinedDataRight.length > 0) {
- // sort by time and by group
- barCombinedDataRight.sort(function (a, b) {
- if (a.x == b.x) {
- return a.groupId - b.groupId;
- } else {
- return a.x - b.x;
- }
- });
- intersections = {};
- this._getDataIntersections(intersections, barCombinedDataRight);
- groupRanges["__barchartRight"] = this._getStackedBarYRange(intersections, barCombinedDataRight);
- groupRanges["__barchartRight"].yAxisOrientation = "right";
- groupIds.push("__barchartRight");
- }
- }
- };
+ var step = new DataStep(this.range.start, this.range.end, minimumStep, this.dom.frame.offsetHeight, this.options.customRange[this.options.orientation]);
+ this.step = step;
+ // get the distance in pixels for a step
+ // dead space is space that is "left over" after a step
+ var stepPixels = (this.dom.frame.offsetHeight - (step.deadSpace * (this.dom.frame.offsetHeight / step.marginRange))) / (((step.marginRange - step.deadSpace) / step.step));
+ this.stepPixels = stepPixels;
- LineGraph.prototype._getStackedBarYRange = function (intersections, combinedData) {
- var key;
- var yMin = combinedData[0].y;
- var yMax = combinedData[0].y;
- for (var i = 0; i < combinedData.length; i++) {
- key = combinedData[i].x;
- if (intersections[key] === undefined) {
- yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin;
- yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax;
- }
- else {
- intersections[key].accumulated += combinedData[i].y;
+ var amountOfSteps = this.height / stepPixels;
+ var stepDifference = 0;
+
+ if (this.master == false) {
+ stepPixels = this.stepPixelsForced;
+ stepDifference = Math.round((this.dom.frame.offsetHeight / stepPixels) - amountOfSteps);
+ for (var i = 0; i < 0.5 * stepDifference; i++) {
+ step.previous();
}
+ amountOfSteps = this.height / stepPixels;
}
- for (var xpos in intersections) {
- if (intersections.hasOwnProperty(xpos)) {
- yMin = yMin > intersections[xpos].accumulated ? intersections[xpos].accumulated : yMin;
- yMax = yMax < intersections[xpos].accumulated ? intersections[xpos].accumulated : yMax;
- }
+ else {
+ amountOfSteps += 0.25;
}
- return {min: yMin, max: yMax};
- };
+ this.valueAtZero = step.marginEnd;
+ var marginStartPos = 0;
- /**
- * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
- * @param {Array} groupIds
- * @param {Object} groupRanges
- * @private
- */
- LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
- var changeCalled = false;
- var yAxisLeftUsed = false;
- var yAxisRightUsed = false;
- var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
- // if groups are present
- if (groupIds.length > 0) {
- for (var i = 0; i < groupIds.length; i++) {
- if (groupRanges.hasOwnProperty(groupIds[i])) {
- if (groupRanges[groupIds[i]].ignore !== true) {
- minVal = groupRanges[groupIds[i]].min;
- maxVal = groupRanges[groupIds[i]].max;
+ // do not draw the first label
+ var max = 1;
- if (groupRanges[groupIds[i]].yAxisOrientation == 'left') {
- yAxisLeftUsed = true;
- minLeft = minLeft > minVal ? minVal : minLeft;
- maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
- }
- else {
- yAxisRightUsed = true;
- minRight = minRight > minVal ? minVal : minRight;
- maxRight = maxRight < maxVal ? maxVal : maxRight;
- }
- }
- }
+ this.maxLabelSize = 0;
+ var y = 0;
+ while (max < Math.round(amountOfSteps)) {
+ step.next();
+ y = Math.round(max * stepPixels);
+ marginStartPos = max * stepPixels;
+ var isMajor = step.isMajor();
+
+ if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
+ this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis minor', this.props.minorCharHeight);
}
- if (yAxisLeftUsed == true) {
- this.yAxisLeft.setRange(minLeft, maxLeft);
+ if (isMajor && this.options['showMajorLabels'] && this.master == true ||
+ this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
+ if (y >= 0) {
+ this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis major', this.props.majorCharHeight);
+ }
+ this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
}
- if (yAxisRightUsed == true) {
- this.yAxisRight.setRange(minRight, maxRight);
+ else {
+ this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
}
- }
- changeCalled = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || changeCalled;
- changeCalled = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || changeCalled;
+ max++;
+ }
- if (yAxisRightUsed == true && yAxisLeftUsed == true) {
- this.yAxisLeft.drawIcons = true;
- this.yAxisRight.drawIcons = true;
+ if (this.master == false) {
+ this.conversionFactor = y / (this.valueAtZero - step.current);
}
else {
- this.yAxisLeft.drawIcons = false;
- this.yAxisRight.drawIcons = false;
+ this.conversionFactor = this.dom.frame.offsetHeight / step.marginRange;
}
- this.yAxisRight.master = !yAxisLeftUsed;
-
- if (this.yAxisRight.master == false) {
- if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;}
- else {this.yAxisLeft.lineOffset = 0;}
-
- changeCalled = this.yAxisLeft.redraw() || changeCalled;
- this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
- changeCalled = this.yAxisRight.redraw() || changeCalled;
+ var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15;
+ // this will resize the yAxis to accomodate the labels.
+ if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
+ this.width = this.maxLabelSize + offset;
+ this.options.width = this.width + "px";
+ DOMutil.cleanupElements(this.DOMelements.lines);
+ DOMutil.cleanupElements(this.DOMelements.labels);
+ this.redraw();
+ return true;
+ }
+ // this will resize the yAxis if it is too big for the labels.
+ else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
+ this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
+ this.options.width = this.width + "px";
+ DOMutil.cleanupElements(this.DOMelements.lines);
+ DOMutil.cleanupElements(this.DOMelements.labels);
+ this.redraw();
+ return true;
}
else {
- changeCalled = this.yAxisRight.redraw() || changeCalled;
+ DOMutil.cleanupElements(this.DOMelements.lines);
+ DOMutil.cleanupElements(this.DOMelements.labels);
+ return false;
}
+ };
- // clean the accumulated lists
- if (groupIds.indexOf("__barchartLeft") != -1) {
- groupIds.splice(groupIds.indexOf("__barchartLeft"),1);
+ DataAxis.prototype.convertValue = function (value) {
+ var invertedValue = this.valueAtZero - value;
+ var convertedValue = invertedValue * this.conversionFactor;
+ return convertedValue;
+ };
+
+ /**
+ * Create a label for the axis at position x
+ * @private
+ * @param y
+ * @param text
+ * @param orientation
+ * @param className
+ * @param characterHeight
+ */
+ DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
+ // reuse redundant label
+ var label = DOMutil.getDOMElement('div',this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();
+ label.className = className;
+ label.innerHTML = text;
+ if (orientation == 'left') {
+ label.style.left = '-' + this.options.labelOffsetX + 'px';
+ label.style.textAlign = "right";
}
- if (groupIds.indexOf("__barchartRight") != -1) {
- groupIds.splice(groupIds.indexOf("__barchartRight"),1);
+ else {
+ label.style.right = '-' + this.options.labelOffsetX + 'px';
+ label.style.textAlign = "left";
}
- return changeCalled;
+ label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
+
+ text += '';
+
+ var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
+ if (this.maxLabelSize < text.length * largestWidth) {
+ this.maxLabelSize = text.length * largestWidth;
+ }
};
/**
- * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function
- *
- * @param {boolean} axisUsed
- * @returns {boolean}
- * @private
- * @param axis
+ * Create a minor line for the axis at position y
+ * @param y
+ * @param orientation
+ * @param className
+ * @param offset
+ * @param width
*/
- LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
- var changed = false;
- if (axisUsed == false) {
- if (axis.dom.frame.parentNode) {
- axis.hide();
- changed = true;
+ DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
+ if (this.master == true) {
+ var line = DOMutil.getDOMElement('div',this.DOMelements.lines, this.dom.lineContainer);//this.dom.redundant.lines.shift();
+ line.className = className;
+ line.innerHTML = '';
+
+ if (orientation == 'left') {
+ line.style.left = (this.width - offset) + 'px';
}
- }
- else {
- if (!axis.dom.frame.parentNode) {
- axis.show();
- changed = true;
+ else {
+ line.style.right = (this.width - offset) + 'px';
}
+
+ line.style.width = width + 'px';
+ line.style.top = y + 'px';
}
- return changed;
};
- /**
- * draw a bar graph
- *
- * @param groupIds
- * @param processedGroupData
- */
- LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) {
- var combinedData = [];
- var intersections = {};
- var coreDistance;
- var key, drawData;
- var group;
- var i,j;
- var barPoints = 0;
- // combine all barchart data
- for (i = 0; i < groupIds.length; i++) {
- group = this.groups[groupIds[i]];
- if (group.options.style == 'bar') {
- if (group.visible == true && (this.options.groups.visibility[groupIds[i]] === undefined || this.options.groups.visibility[groupIds[i]] == true)) {
- for (j = 0; j < processedGroupData[groupIds[i]].length; j++) {
- combinedData.push({
- x: processedGroupData[groupIds[i]][j].x,
- y: processedGroupData[groupIds[i]][j].y,
- groupId: groupIds[i]
- });
- barPoints += 1;
- }
- }
- }
- }
- if (barPoints == 0) {return;}
- // sort by time and by group
- combinedData.sort(function (a, b) {
- if (a.x == b.x) {
- return a.groupId - b.groupId;
- } else {
- return a.x - b.x;
- }
- });
+ /**
+ * Determine the size of text on the axis (both major and minor axis).
+ * The size is calculated only once and then cached in this.props.
+ * @private
+ */
+ DataAxis.prototype._calculateCharSize = function () {
+ // determine the char width and height on the minor axis
+ if (!('minorCharHeight' in this.props)) {
+ var textMinor = document.createTextNode('0');
+ var measureCharMinor = document.createElement('DIV');
+ measureCharMinor.className = 'yAxis minor measure';
+ measureCharMinor.appendChild(textMinor);
+ this.dom.frame.appendChild(measureCharMinor);
- // get intersections
- this._getDataIntersections(intersections, combinedData);
+ this.props.minorCharHeight = measureCharMinor.clientHeight;
+ this.props.minorCharWidth = measureCharMinor.clientWidth;
- // plot barchart
- for (i = 0; i < combinedData.length; i++) {
- group = this.groups[combinedData[i].groupId];
- var minWidth = 0.1 * group.options.barChart.width;
+ this.dom.frame.removeChild(measureCharMinor);
+ }
- key = combinedData[i].x;
- var heightOffset = 0;
- if (intersections[key] === undefined) {
- if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);}
- if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));}
- drawData = this._getSafeDrawData(coreDistance, group, minWidth);
- }
- else {
- var nextKey = i + (intersections[key].amount - intersections[key].resolved);
- var prevKey = i - (intersections[key].resolved + 1);
- if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);}
- if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));}
- drawData = this._getSafeDrawData(coreDistance, group, minWidth);
- intersections[key].resolved += 1;
+ if (!('majorCharHeight' in this.props)) {
+ var textMajor = document.createTextNode('0');
+ var measureCharMajor = document.createElement('DIV');
+ measureCharMajor.className = 'yAxis major measure';
+ measureCharMajor.appendChild(textMajor);
+ this.dom.frame.appendChild(measureCharMajor);
- if (group.options.barChart.handleOverlap == 'stack') {
- heightOffset = intersections[key].accumulated;
- intersections[key].accumulated += group.zeroPosition - combinedData[i].y;
- }
- else if (group.options.barChart.handleOverlap == 'sideBySide') {
- drawData.width = drawData.width / intersections[key].amount;
- drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
- if (group.options.barChart.align == 'left') {drawData.offset -= 0.5*drawData.width;}
- else if (group.options.barChart.align == 'right') {drawData.offset += 0.5*drawData.width;}
- }
- }
- DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' bar', this.svgElements, this.svg);
- // draw points
- if (group.options.drawPoints.enabled == true) {
- DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, group, this.svgElements, this.svg);
- }
+ this.props.majorCharHeight = measureCharMajor.clientHeight;
+ this.props.majorCharWidth = measureCharMajor.clientWidth;
+
+ this.dom.frame.removeChild(measureCharMajor);
}
};
/**
- * Fill the intersections object with counters of how many datapoints share the same x coordinates
- * @param intersections
- * @param combinedData
- * @private
+ * Snap a date to a rounded value.
+ * The snap intervals are dependent on the current scale and step.
+ * @param {Date} date the date to be snapped.
+ * @return {Date} snappedDate
*/
- LineGraph.prototype._getDataIntersections = function (intersections, combinedData) {
- // get intersections
- var coreDistance;
- for (var i = 0; i < combinedData.length; i++) {
- if (i + 1 < combinedData.length) {
- coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
- }
- if (i > 0) {
- coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
- }
- if (coreDistance == 0) {
- if (intersections[combinedData[i].x] === undefined) {
- intersections[combinedData[i].x] = {amount: 0, resolved: 0, accumulated: 0};
- }
- intersections[combinedData[i].x].amount += 1;
- }
- }
+ DataAxis.prototype.snap = function(date) {
+ return this.step.snap(date);
};
+ module.exports = DataAxis;
+
+
+/***/ },
+/* 42 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var util = __webpack_require__(1);
+ var DOMutil = __webpack_require__(6);
+
/**
- * Get the width and offset for bargraphs based on the coredistance between datapoints
- *
- * @param coreDistance
- * @param group
- * @param minWidth
- * @returns {{width: Number, offset: Number}}
- * @private
+ * @constructor Group
+ * @param {Number | String} groupId
+ * @param {Object} data
+ * @param {ItemSet} itemSet
*/
- LineGraph.prototype._getSafeDrawData = function (coreDistance, group, minWidth) {
- var width, offset;
- if (coreDistance < group.options.barChart.width && coreDistance > 0) {
- width = coreDistance < minWidth ? minWidth : coreDistance;
+ function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
+ this.id = groupId;
+ var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
+ this.options = util.selectiveBridgeObject(fields,options);
+ this.usingDefaultStyle = group.className === undefined;
+ this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
+ this.zeroPosition = 0;
+ this.update(group);
+ if (this.usingDefaultStyle == true) {
+ this.groupsUsingDefaultStyles[0] += 1;
+ }
+ this.itemsData = [];
+ this.visible = group.visible === undefined ? true : group.visible;
+ }
- offset = 0; // recalculate offset with the new width;
- if (group.options.barChart.align == 'left') {
- offset -= 0.5 * coreDistance;
- }
- else if (group.options.barChart.align == 'right') {
- offset += 0.5 * coreDistance;
+ GraphGroup.prototype.setItems = function(items) {
+ if (items != null) {
+ this.itemsData = items;
+ if (this.options.sort == true) {
+ this.itemsData.sort(function (a,b) {return a.x - b.x;})
}
}
else {
- // default settings
- width = group.options.barChart.width;
- offset = 0;
- if (group.options.barChart.align == 'left') {
- offset -= 0.5 * group.options.barChart.width;
- }
- else if (group.options.barChart.align == 'right') {
- offset += 0.5 * group.options.barChart.width;
- }
+ this.itemsData = [];
}
-
- return {width: width, offset: offset};
};
+ GraphGroup.prototype.setZeroPosition = function(pos) {
+ this.zeroPosition = pos;
+ };
- /**
- * draw a line graph
- *
- * @param dataset
- * @param group
- */
- LineGraph.prototype._drawLineGraph = function (dataset, group) {
- if (dataset != null) {
- if (dataset.length > 0) {
- var path, d;
- var svgHeight = Number(this.svg.style.height.replace("px",""));
- path = DOMutil.getSVGElement('path', this.svgElements, this.svg);
- path.setAttributeNS(null, "class", group.className);
+ GraphGroup.prototype.setOptions = function(options) {
+ if (options !== undefined) {
+ var fields = ['sampling','style','sort','yAxisOrientation','barChart'];
+ util.selectiveDeepExtend(fields, this.options, options);
- // construct path from dataset
- if (group.options.catmullRom.enabled == true) {
- d = this._catmullRom(dataset, group);
- }
- else {
- d = this._linear(dataset);
- }
+ util.mergeOptions(this.options, options,'catmullRom');
+ util.mergeOptions(this.options, options,'drawPoints');
+ util.mergeOptions(this.options, options,'shaded');
- // append with points for fill and finalize the path
- if (group.options.shaded.enabled == true) {
- var fillPath = DOMutil.getSVGElement('path',this.svgElements, this.svg);
- var dFill;
- if (group.options.shaded.orientation == 'top') {
- dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0;
- }
- else {
- dFill = "M" + dataset[0].x + "," + svgHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + svgHeight;
+ if (options.catmullRom) {
+ if (typeof options.catmullRom == 'object') {
+ if (options.catmullRom.parametrization) {
+ if (options.catmullRom.parametrization == 'uniform') {
+ this.options.catmullRom.alpha = 0;
+ }
+ else if (options.catmullRom.parametrization == 'chordal') {
+ this.options.catmullRom.alpha = 1.0;
+ }
+ else {
+ this.options.catmullRom.parametrization = 'centripetal';
+ this.options.catmullRom.alpha = 0.5;
+ }
}
- fillPath.setAttributeNS(null, "class", group.className + " fill");
- fillPath.setAttributeNS(null, "d", dFill);
- }
- // copy properties to path for drawing.
- path.setAttributeNS(null, "d", "M" + d);
-
- // draw points
- if (group.options.drawPoints.enabled == true) {
- this._drawPoints(dataset, group, this.svgElements, this.svg);
}
}
}
};
- /**
- * draw the data points
- *
- * @param {Array} dataset
- * @param {Object} JSONcontainer
- * @param {Object} svg | SVG DOM element
- * @param {GraphGroup} group
- * @param {Number} [offset]
- */
- LineGraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg, offset) {
- if (offset === undefined) {offset = 0;}
- for (var i = 0; i < dataset.length; i++) {
- DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, JSONcontainer, svg);
- }
+ GraphGroup.prototype.update = function(group) {
+ this.group = group;
+ this.content = group.content || 'graph';
+ this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10;
+ this.visible = group.visible === undefined ? true : group.visible;
+ this.setOptions(group.options);
};
+ GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
+ var fillHeight = iconHeight * 0.5;
+ var path, fillPath;
+ var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer);
+ outline.setAttributeNS(null, "x", x);
+ outline.setAttributeNS(null, "y", y - fillHeight);
+ outline.setAttributeNS(null, "width", iconWidth);
+ outline.setAttributeNS(null, "height", 2*fillHeight);
+ outline.setAttributeNS(null, "class", "outline");
- /**
- * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
- * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
- * the yAxis.
- *
- * @param datapoints
- * @returns {Array}
- * @private
- */
- LineGraph.prototype._convertXcoordinates = function (datapoints) {
- var extractedData = [];
- var xValue, yValue;
- var toScreen = this.body.util.toScreen;
+ if (this.options.style == 'line') {
+ path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
+ path.setAttributeNS(null, "class", this.className);
+ path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
+ if (this.options.shaded.enabled == true) {
+ fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
+ if (this.options.shaded.orientation == 'top') {
+ fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
+ "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
+ }
+ else {
+ fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
+ "L"+x+"," + (y + fillHeight) + " " +
+ "L"+ (x + iconWidth) + "," + (y + fillHeight) +
+ "L"+ (x + iconWidth) + ","+y);
+ }
+ fillPath.setAttributeNS(null, "class", this.className + " iconFill");
+ }
- for (var i = 0; i < datapoints.length; i++) {
- xValue = toScreen(datapoints[i].x) + this.width;
- yValue = datapoints[i].y;
- extractedData.push({x: xValue, y: yValue});
+ if (this.options.drawPoints.enabled == true) {
+ DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer);
+ }
}
+ else {
+ var barWidth = Math.round(0.3 * iconWidth);
+ var bar1Height = Math.round(0.4 * iconHeight);
+ var bar2Height = Math.round(0.75 * iconHeight);
- return extractedData;
- };
-
+ var offset = Math.round((iconWidth - (2 * barWidth))/3);
+ DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer);
+ DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer);
+ }
+ };
/**
- * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
- * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
- * the yAxis.
*
- * @param datapoints
- * @returns {Array}
- * @private
+ * @param iconWidth
+ * @param iconHeight
+ * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}}
*/
- LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
- var extractedData = [];
- var xValue, yValue;
- var toScreen = this.body.util.toScreen;
- var axis = this.yAxisLeft;
- var svgHeight = Number(this.svg.style.height.replace("px",""));
- if (group.options.yAxisOrientation == 'right') {
- axis = this.yAxisRight;
- }
+ GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) {
+ var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
+ this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight);
+ return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation};
+ }
- for (var i = 0; i < datapoints.length; i++) {
- xValue = toScreen(datapoints[i].x) + this.width;
- yValue = Math.round(axis.convertValue(datapoints[i].y));
- extractedData.push({x: xValue, y: yValue});
- }
+ module.exports = GraphGroup;
- group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
- return extractedData;
- };
+/***/ },
+/* 43 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var util = __webpack_require__(1);
+ var DOMutil = __webpack_require__(6);
+ var Component = __webpack_require__(22);
/**
- * This uses an uniform parametrization of the CatmullRom algorithm:
- * "On the Parameterization of Catmull-Rom Curves" by Cem Yuksel et al.
- * @param data
- * @returns {string}
- * @private
+ * Legend for Graph2d
*/
- LineGraph.prototype._catmullRomUniform = function(data) {
- // catmull rom
- var p0, p1, p2, p3, bp1, bp2;
- var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
- var normalization = 1/6;
- var length = data.length;
- for (var i = 0; i < length - 1; i++) {
+ function Legend(body, options, side, linegraphOptions) {
+ this.body = body;
+ this.defaultOptions = {
+ enabled: true,
+ icons: true,
+ iconSize: 20,
+ iconSpacing: 6,
+ left: {
+ visible: true,
+ position: 'top-left' // top/bottom - left,center,right
+ },
+ right: {
+ visible: true,
+ position: 'top-left' // top/bottom - left,center,right
+ }
+ }
+ this.side = side;
+ this.options = util.extend({},this.defaultOptions);
+ this.linegraphOptions = linegraphOptions;
- p0 = (i == 0) ? data[0] : data[i-1];
- p1 = data[i];
- p2 = data[i+1];
- p3 = (i + 2 < length) ? data[i+2] : p2;
+ this.svgElements = {};
+ this.dom = {};
+ this.groups = {};
+ this.amountOfGroups = 0;
+ this._create();
+ this.setOptions(options);
+ }
- // Catmull-Rom to Cubic Bezier conversion matrix
- // 0 1 0 0
- // -1/6 1 1/6 0
- // 0 1/6 1 -1/6
- // 0 0 1 0
+ Legend.prototype = new Component();
- // bp0 = { x: p1.x, y: p1.y };
- bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
- bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
- // bp0 = { x: p2.x, y: p2.y };
- d += "C" +
- bp1.x + "," +
- bp1.y + " " +
- bp2.x + "," +
- bp2.y + " " +
- p2.x + "," +
- p2.y + " ";
+ Legend.prototype.addGroup = function(label, graphOptions) {
+ if (!this.groups.hasOwnProperty(label)) {
+ this.groups[label] = graphOptions;
}
+ this.amountOfGroups += 1;
+ };
- return d;
+ Legend.prototype.updateGroup = function(label, graphOptions) {
+ this.groups[label] = graphOptions;
};
- /**
- * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm.
- * By default, the centripetal parameterization is used because this gives the nicest results.
- * These parameterizations are relatively heavy because the distance between 4 points have to be calculated.
- *
- * One optimization can be used to reuse distances since this is a sliding window approach.
- * @param data
- * @returns {string}
- * @private
- */
- LineGraph.prototype._catmullRom = function(data, group) {
- var alpha = group.options.catmullRom.alpha;
- if (alpha == 0 || alpha === undefined) {
- return this._catmullRomUniform(data);
+ Legend.prototype.removeGroup = function(label) {
+ if (this.groups.hasOwnProperty(label)) {
+ delete this.groups[label];
+ this.amountOfGroups -= 1;
}
- else {
- var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
- var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
- var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
- var length = data.length;
- for (var i = 0; i < length - 1; i++) {
-
- p0 = (i == 0) ? data[0] : data[i-1];
- p1 = data[i];
- p2 = data[i+1];
- p3 = (i + 2 < length) ? data[i+2] : p2;
+ };
- d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
- d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
- d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
+ Legend.prototype._create = function() {
+ this.dom.frame = document.createElement('div');
+ this.dom.frame.className = 'legend';
+ this.dom.frame.style.position = "absolute";
+ this.dom.frame.style.top = "10px";
+ this.dom.frame.style.display = "block";
- // Catmull-Rom to Cubic Bezier conversion matrix
- //
- // A = 2d1^2a + 3d1^a * d2^a + d3^2a
- // B = 2d3^2a + 3d3^a * d2^a + d2^2a
- //
- // [ 0 1 0 0 ]
- // [ -d2^2a/N A/N d1^2a/N 0 ]
- // [ 0 d3^2a/M B/M -d2^2a/M ]
- // [ 0 0 1 0 ]
+ this.dom.textArea = document.createElement('div');
+ this.dom.textArea.className = 'legendText';
+ this.dom.textArea.style.position = "relative";
+ this.dom.textArea.style.top = "0px";
- // [ 0 1 0 0 ]
- // [ -d2pow2a/N A/N d1pow2a/N 0 ]
- // [ 0 d3pow2a/M B/M -d2pow2a/M ]
- // [ 0 0 1 0 ]
+ this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
+ this.svg.style.position = 'absolute';
+ this.svg.style.top = 0 +'px';
+ this.svg.style.width = this.options.iconSize + 5 + 'px';
- d3powA = Math.pow(d3, alpha);
- d3pow2A = Math.pow(d3,2*alpha);
- d2powA = Math.pow(d2, alpha);
- d2pow2A = Math.pow(d2,2*alpha);
- d1powA = Math.pow(d1, alpha);
- d1pow2A = Math.pow(d1,2*alpha);
+ this.dom.frame.appendChild(this.svg);
+ this.dom.frame.appendChild(this.dom.textArea);
+ };
- A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
- B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
- N = 3*d1powA * (d1powA + d2powA);
- if (N > 0) {N = 1 / N;}
- M = 3*d3powA * (d3powA + d2powA);
- if (M > 0) {M = 1 / M;}
+ /**
+ * Hide the component from the DOM
+ */
+ Legend.prototype.hide = function() {
+ // remove the frame containing the items
+ if (this.dom.frame.parentNode) {
+ this.dom.frame.parentNode.removeChild(this.dom.frame);
+ }
+ };
- bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
- y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
+ /**
+ * Show the component in the DOM (when not already visible).
+ * @return {Boolean} changed
+ */
+ Legend.prototype.show = function() {
+ // show frame containing the items
+ if (!this.dom.frame.parentNode) {
+ this.body.dom.center.appendChild(this.dom.frame);
+ }
+ };
- bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
- y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
+ Legend.prototype.setOptions = function(options) {
+ var fields = ['enabled','orientation','icons','left','right'];
+ util.selectiveDeepExtend(fields, this.options, options);
+ };
- if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
- if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
- d += "C" +
- bp1.x + "," +
- bp1.y + " " +
- bp2.x + "," +
- bp2.y + " " +
- p2.x + "," +
- p2.y + " ";
+ Legend.prototype.redraw = function() {
+ var activeGroups = 0;
+ for (var groupId in this.groups) {
+ if (this.groups.hasOwnProperty(groupId)) {
+ if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
+ activeGroups++;
+ }
}
+ }
- return d;
+ if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) {
+ this.hide();
}
- };
+ else {
+ this.show();
+ if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') {
+ this.dom.frame.style.left = '4px';
+ this.dom.frame.style.textAlign = "left";
+ this.dom.textArea.style.textAlign = "left";
+ this.dom.textArea.style.left = (this.options.iconSize + 15) + 'px';
+ this.dom.textArea.style.right = '';
+ this.svg.style.left = 0 +'px';
+ this.svg.style.right = '';
+ }
+ else {
+ this.dom.frame.style.right = '4px';
+ this.dom.frame.style.textAlign = "right";
+ this.dom.textArea.style.textAlign = "right";
+ this.dom.textArea.style.right = (this.options.iconSize + 15) + 'px';
+ this.dom.textArea.style.left = '';
+ this.svg.style.right = 0 +'px';
+ this.svg.style.left = '';
+ }
- /**
- * this generates the SVG path for a linear drawing between datapoints.
- * @param data
- * @returns {string}
- * @private
- */
- LineGraph.prototype._linear = function(data) {
- // linear
- var d = "";
- for (var i = 0; i < data.length; i++) {
- if (i == 0) {
- d += data[i].x + "," + data[i].y;
+ if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') {
+ this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
+ this.dom.frame.style.bottom = '';
}
else {
- d += " " + data[i].x + "," + data[i].y;
+ this.dom.frame.style.bottom = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
+ this.dom.frame.style.top = '';
+ }
+
+ if (this.options.icons == false) {
+ this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px';
+ this.dom.textArea.style.right = '';
+ this.dom.textArea.style.left = '';
+ this.svg.style.width = '0px';
+ }
+ else {
+ this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'
+ this.drawLegendIcons();
+ }
+
+ var content = '';
+ for (var groupId in this.groups) {
+ if (this.groups.hasOwnProperty(groupId)) {
+ if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
+ content += this.groups[groupId].content + '
';
+ }
+ }
}
+ this.dom.textArea.innerHTML = content;
+ this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px';
}
- return d;
};
- module.exports = LineGraph;
+ Legend.prototype.drawLegendIcons = function() {
+ if (this.dom.frame.parentNode) {
+ DOMutil.prepareElements(this.svgElements);
+ var padding = window.getComputedStyle(this.dom.frame).paddingTop;
+ var iconOffset = Number(padding.replace('px',''));
+ var x = iconOffset;
+ var iconWidth = this.options.iconSize;
+ var iconHeight = 0.75 * this.options.iconSize;
+ var y = iconOffset + 0.5 * iconHeight + 3;
+
+ this.svg.style.width = iconWidth + 5 + iconOffset + 'px';
+
+ for (var groupId in this.groups) {
+ if (this.groups.hasOwnProperty(groupId)) {
+ if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
+ this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
+ y += iconHeight + this.options.iconSpacing;
+ }
+ }
+ }
+
+ DOMutil.cleanupElements(this.svgElements);
+ }
+ };
+
+ module.exports = Legend;
/***/ },
-/* 41 */
+/* 44 */
/***/ function(module, exports, __webpack_require__) {
var util = __webpack_require__(1);
var DOMutil = __webpack_require__(6);
+ var DataSet = __webpack_require__(7);
+ var DataView = __webpack_require__(8);
var Component = __webpack_require__(22);
- var DataStep = __webpack_require__(42);
+ var DataAxis = __webpack_require__(41);
+ var GraphGroup = __webpack_require__(42);
+ var Legend = __webpack_require__(43);
+
+ var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
/**
- * A horizontal time axis
- * @param {Object} [options] See DataAxis.setOptions for the available
- * options.
- * @constructor DataAxis
- * @extends Component
+ * This is the constructor of the LineGraph. It requires a Timeline body and options.
+ *
* @param body
+ * @param options
+ * @constructor
*/
- function DataAxis (body, options, svg, linegraphOptions) {
+ function LineGraph(body, options) {
this.id = util.randomUUID();
this.body = body;
this.defaultOptions = {
- orientation: 'left', // supported: 'left', 'right'
- showMinorLabels: true,
- showMajorLabels: true,
- icons: true,
- majorLinesOffset: 7,
- minorLinesOffset: 4,
- labelOffsetX: 10,
- labelOffsetY: 2,
- iconWidth: 20,
- width: '40px',
- visible: true,
- customRange: {
- left: {min:undefined, max:undefined},
- right: {min:undefined, max:undefined}
+ yAxisOrientation: 'left',
+ defaultGroup: 'default',
+ sort: true,
+ sampling: true,
+ graphHeight: '400px',
+ shaded: {
+ enabled: false,
+ orientation: 'bottom' // top, bottom
+ },
+ style: 'line', // line, bar
+ barChart: {
+ width: 50,
+ handleOverlap: 'overlap',
+ align: 'center' // left, center, right
+ },
+ catmullRom: {
+ enabled: true,
+ parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5)
+ alpha: 0.5
+ },
+ drawPoints: {
+ enabled: true,
+ size: 6,
+ style: 'square' // square, circle
+ },
+ dataAxis: {
+ showMinorLabels: true,
+ showMajorLabels: true,
+ icons: false,
+ width: '40px',
+ visible: true,
+ customRange: {
+ left: {min:undefined, max:undefined},
+ right: {min:undefined, max:undefined}
+ }
+ },
+ legend: {
+ enabled: false,
+ icons: true,
+ left: {
+ visible: true,
+ position: 'top-left' // top/bottom - left,right
+ },
+ right: {
+ visible: true,
+ position: 'top-right' // top/bottom - left,right
+ }
+ },
+ groups: {
+ visibility: {}
}
};
- this.linegraphOptions = linegraphOptions;
- this.linegraphSVG = svg;
- this.props = {};
- this.DOMelements = { // dynamic elements
- lines: {},
- labels: {}
- };
-
- this.dom = {};
-
- this.range = {start:0, end:0};
-
+ // options is shared by this ItemSet and all its items
this.options = util.extend({}, this.defaultOptions);
- this.conversionFactor = 1;
-
- this.setOptions(options);
- this.width = Number(('' + this.options.width).replace("px",""));
- this.minWidth = this.width;
- this.height = this.linegraphSVG.offsetHeight;
-
- this.stepPixels = 25;
- this.stepPixelsForced = 25;
- this.lineOffset = 0;
- this.master = true;
- this.svgElements = {};
-
-
+ this.dom = {};
+ this.props = {};
+ this.hammer = null;
this.groups = {};
- this.amountOfGroups = 0;
+ this.abortedGraphUpdate = false;
- // create the HTML DOM
- this._create();
- }
+ var me = this;
+ this.itemsData = null; // DataSet
+ this.groupsData = null; // DataSet
- DataAxis.prototype = new Component();
-
-
-
- DataAxis.prototype.addGroup = function(label, graphOptions) {
- if (!this.groups.hasOwnProperty(label)) {
- this.groups[label] = graphOptions;
- }
- this.amountOfGroups += 1;
- };
-
- DataAxis.prototype.updateGroup = function(label, graphOptions) {
- this.groups[label] = graphOptions;
- };
+ // listeners for the DataSet of the items
+ this.itemListeners = {
+ 'add': function (event, params, senderId) {
+ me._onAdd(params.items);
+ },
+ 'update': function (event, params, senderId) {
+ me._onUpdate(params.items);
+ },
+ 'remove': function (event, params, senderId) {
+ me._onRemove(params.items);
+ }
+ };
- DataAxis.prototype.removeGroup = function(label) {
- if (this.groups.hasOwnProperty(label)) {
- delete this.groups[label];
- this.amountOfGroups -= 1;
- }
- };
+ // 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;
+ this.touchParams = {}; // stores properties while dragging
- 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',
- 'icons',
- 'majorLinesOffset',
- 'minorLinesOffset',
- 'labelOffsetX',
- 'labelOffsetY',
- 'iconWidth',
- 'width',
- 'visible',
- 'customRange'
- ];
- util.selectiveExtend(fields, this.options, options);
+ this.svgElements = {};
+ this.setOptions(options);
+ this.groupsUsingDefaultStyles = [0];
- this.minWidth = Number(('' + this.options.width).replace("px",""));
+ this.body.emitter.on("rangechanged", function() {
+ me.lastStart = me.body.range.start;
+ me.svg.style.left = util.option.asSize(-me.width);
+ me._updateGraph.apply(me);
+ });
- if (redraw == true && this.dom.frame) {
- this.hide();
- this.show();
- }
- }
- };
+ // create the HTML DOM
+ this._create();
+ this.body.emitter.emit("change");
+ }
+ LineGraph.prototype = new Component();
/**
- * Create the HTML DOM for the DataAxis
+ * Create the HTML DOM for the ItemSet
*/
- DataAxis.prototype._create = function() {
- this.dom.frame = document.createElement('div');
- this.dom.frame.style.width = this.options.width;
- this.dom.frame.style.height = this.height;
-
- this.dom.lineContainer = document.createElement('div');
- this.dom.lineContainer.style.width = '100%';
- this.dom.lineContainer.style.height = this.height;
+ LineGraph.prototype._create = function(){
+ var frame = document.createElement('div');
+ frame.className = 'LineGraph';
+ this.dom.frame = frame;
// create svg element for graph drawing.
this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
- this.svg.style.position = "absolute";
- this.svg.style.top = '0px';
- this.svg.style.height = '100%';
- this.svg.style.width = '100%';
+ this.svg.style.position = "relative";
+ this.svg.style.height = ('' + this.options.graphHeight).replace("px",'') + 'px';
this.svg.style.display = "block";
- this.dom.frame.appendChild(this.svg);
- };
-
- DataAxis.prototype._redrawGroupIcons = function () {
- DOMutil.prepareElements(this.svgElements);
+ frame.appendChild(this.svg);
- var x;
- var iconWidth = this.options.iconWidth;
- var iconHeight = 15;
- var iconOffset = 4;
- var y = iconOffset + 0.5 * iconHeight;
+ // data axis
+ this.options.dataAxis.orientation = 'left';
+ this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
- if (this.options.orientation == 'left') {
- x = iconOffset;
- }
- else {
- x = this.width - iconWidth - iconOffset;
- }
+ this.options.dataAxis.orientation = 'right';
+ this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
+ delete this.options.dataAxis.orientation;
- for (var groupId in this.groups) {
- if (this.groups.hasOwnProperty(groupId)) {
- if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
- this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
- y += iconHeight + iconOffset;
- }
- }
- }
+ // legends
+ this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups);
+ this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups);
- DOMutil.cleanupElements(this.svgElements);
+ this.show();
};
/**
- * Create the HTML DOM for the DataAxis
+ * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
+ * @param options
*/
- DataAxis.prototype.show = function() {
- if (!this.dom.frame.parentNode) {
- if (this.options.orientation == 'left') {
- this.body.dom.left.appendChild(this.dom.frame);
+ LineGraph.prototype.setOptions = function(options) {
+ if (options) {
+ var fields = ['sampling','defaultGroup','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort','groups'];
+ util.selectiveDeepExtend(fields, this.options, options);
+ util.mergeOptions(this.options, options,'catmullRom');
+ util.mergeOptions(this.options, options,'drawPoints');
+ util.mergeOptions(this.options, options,'shaded');
+ util.mergeOptions(this.options, options,'legend');
+
+ if (options.catmullRom) {
+ if (typeof options.catmullRom == 'object') {
+ if (options.catmullRom.parametrization) {
+ if (options.catmullRom.parametrization == 'uniform') {
+ this.options.catmullRom.alpha = 0;
+ }
+ else if (options.catmullRom.parametrization == 'chordal') {
+ this.options.catmullRom.alpha = 1.0;
+ }
+ else {
+ this.options.catmullRom.parametrization = 'centripetal';
+ this.options.catmullRom.alpha = 0.5;
+ }
+ }
+ }
}
- else {
- this.body.dom.right.appendChild(this.dom.frame);
+
+ if (this.yAxisLeft) {
+ if (options.dataAxis !== undefined) {
+ this.yAxisLeft.setOptions(this.options.dataAxis);
+ this.yAxisRight.setOptions(this.options.dataAxis);
+ }
}
- }
- if (!this.dom.lineContainer.parentNode) {
- this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
+ if (this.legendLeft) {
+ if (options.legend !== undefined) {
+ this.legendLeft.setOptions(this.options.legend);
+ this.legendRight.setOptions(this.options.legend);
+ }
+ }
+
+ if (this.groups.hasOwnProperty(UNGROUPED)) {
+ this.groups[UNGROUPED].setOptions(options);
+ }
+ }
+ if (this.dom.frame) {
+ this._updateGraph();
}
};
/**
- * Create the HTML DOM for the DataAxis
+ * Hide the component from the DOM
*/
- DataAxis.prototype.hide = function() {
+ LineGraph.prototype.hide = function() {
+ // remove the frame containing the items
if (this.dom.frame.parentNode) {
this.dom.frame.parentNode.removeChild(this.dom.frame);
}
-
- if (this.dom.lineContainer.parentNode) {
- this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
- }
};
/**
- * Set a range (start and end)
- * @param end
- * @param start
- * @param end
+ * Show the component in the DOM (when not already visible).
+ * @return {Boolean} changed
*/
- DataAxis.prototype.setRange = function (start, end) {
- this.range.start = start;
- this.range.end = end;
+ LineGraph.prototype.show = function() {
+ // show frame containing the items
+ if (!this.dom.frame.parentNode) {
+ this.body.dom.center.appendChild(this.dom.frame);
+ }
};
+
/**
- * Repaint the component
- * @return {boolean} Returns true if the component is resized
+ * Set items
+ * @param {vis.DataSet | null} items
*/
- DataAxis.prototype.redraw = function () {
- var changeCalled = false;
- var activeGroups = 0;
- for (var groupId in this.groups) {
- if (this.groups.hasOwnProperty(groupId)) {
- if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
- activeGroups++;
- }
- }
+ LineGraph.prototype.setItems = function(items) {
+ var me = this,
+ ids,
+ oldItemsData = this.itemsData;
+
+ // replace the dataset
+ if (!items) {
+ this.itemsData = null;
}
- if (this.amountOfGroups == 0 || activeGroups == 0) {
- this.hide();
+ else if (items instanceof DataSet || items instanceof DataView) {
+ this.itemsData = items;
}
else {
- this.show();
- this.height = Number(this.linegraphSVG.style.height.replace("px",""));
- // svg offsetheight did not work in firefox and explorer...
+ throw new TypeError('Data must be an instance of DataSet or DataView');
+ }
- this.dom.lineContainer.style.height = this.height + 'px';
- this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
-
- var props = this.props;
- var frame = this.dom.frame;
-
- // update classname
- frame.className = 'dataaxis';
-
- // calculate character width and height
- this._calculateCharSize();
-
- var orientation = this.options.orientation;
- var showMinorLabels = this.options.showMinorLabels;
- var showMajorLabels = this.options.showMajorLabels;
+ if (oldItemsData) {
+ // unsubscribe from old dataset
+ util.forEach(this.itemListeners, function (callback, event) {
+ oldItemsData.off(event, callback);
+ });
- // determine the width and height of the elemens for the axis
- props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
- props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
+ // remove all drawn items
+ ids = oldItemsData.getIds();
+ this._onRemove(ids);
+ }
- props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
- props.minorLineHeight = 1;
- props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
- props.majorLineHeight = 1;
+ if (this.itemsData) {
+ // subscribe to new dataset
+ var id = this.id;
+ util.forEach(this.itemListeners, function (callback, event) {
+ me.itemsData.on(event, callback, id);
+ });
- // take frame offline while updating (is almost twice as fast)
- if (orientation == 'left') {
- frame.style.top = '0';
- frame.style.left = '0';
- frame.style.bottom = '';
- frame.style.width = this.width + 'px';
- frame.style.height = this.height + "px";
- }
- else { // right
- frame.style.top = '';
- frame.style.bottom = '0';
- frame.style.left = '0';
- frame.style.width = this.width + 'px';
- frame.style.height = this.height + "px";
- }
- changeCalled = this._redrawLabels();
- if (this.options.icons == true) {
- this._redrawGroupIcons();
- }
+ // add all new items
+ ids = this.itemsData.getIds();
+ this._onAdd(ids);
}
- return changeCalled;
+ this._updateUngrouped();
+ this._updateGraph();
+ this.redraw();
};
/**
- * Repaint major and minor text labels and vertical grid lines
- * @private
+ * Set groups
+ * @param {vis.DataSet} groups
*/
- DataAxis.prototype._redrawLabels = function () {
- DOMutil.prepareElements(this.DOMelements.lines);
- DOMutil.prepareElements(this.DOMelements.labels);
-
- var orientation = this.options['orientation'];
-
- // calculate range and step (step such that we have space for 7 characters per label)
- var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
-
- var step = new DataStep(this.range.start, this.range.end, minimumStep, this.dom.frame.offsetHeight, this.options.customRange[this.options.orientation]);
- this.step = step;
- // get the distance in pixels for a step
- // dead space is space that is "left over" after a step
- var stepPixels = (this.dom.frame.offsetHeight - (step.deadSpace * (this.dom.frame.offsetHeight / step.marginRange))) / (((step.marginRange - step.deadSpace) / step.step));
- this.stepPixels = stepPixels;
+ LineGraph.prototype.setGroups = function(groups) {
+ var me = this,
+ ids;
- var amountOfSteps = this.height / stepPixels;
- var stepDifference = 0;
+ // unsubscribe from current dataset
+ if (this.groupsData) {
+ util.forEach(this.groupListeners, function (callback, event) {
+ me.groupsData.unsubscribe(event, callback);
+ });
- if (this.master == false) {
- stepPixels = this.stepPixelsForced;
- stepDifference = Math.round((this.dom.frame.offsetHeight / stepPixels) - amountOfSteps);
- for (var i = 0; i < 0.5 * stepDifference; i++) {
- step.previous();
- }
- amountOfSteps = this.height / stepPixels;
- }
- else {
- amountOfSteps += 0.25;
+ // remove all drawn groups
+ ids = this.groupsData.getIds();
+ this.groupsData = null;
+ this._onRemoveGroups(ids); // note: this will cause a redraw
}
-
- this.valueAtZero = step.marginEnd;
- var marginStartPos = 0;
-
- // do not draw the first label
- var max = 1;
-
- this.maxLabelSize = 0;
- var y = 0;
- while (max < Math.round(amountOfSteps)) {
- step.next();
- y = Math.round(max * stepPixels);
- marginStartPos = max * stepPixels;
- var isMajor = step.isMajor();
-
- if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
- this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis minor', this.props.minorCharHeight);
- }
-
- if (isMajor && this.options['showMajorLabels'] && this.master == true ||
- this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
- if (y >= 0) {
- this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis major', this.props.majorCharHeight);
- }
- this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
- }
- else {
- this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
- }
-
- max++;
+ // replace the dataset
+ if (!groups) {
+ this.groupsData = null;
}
-
- if (this.master == false) {
- this.conversionFactor = y / (this.valueAtZero - step.current);
+ else if (groups instanceof DataSet || groups instanceof DataView) {
+ this.groupsData = groups;
}
else {
- this.conversionFactor = this.dom.frame.offsetHeight / step.marginRange;
+ throw new TypeError('Data must be an instance of DataSet or DataView');
}
- var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15;
- // this will resize the yAxis to accomodate the labels.
- if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
- this.width = this.maxLabelSize + offset;
- this.options.width = this.width + "px";
- DOMutil.cleanupElements(this.DOMelements.lines);
- DOMutil.cleanupElements(this.DOMelements.labels);
- this.redraw();
- return true;
- }
- // this will resize the yAxis if it is too big for the labels.
- else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
- this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
- this.options.width = this.width + "px";
- DOMutil.cleanupElements(this.DOMelements.lines);
- DOMutil.cleanupElements(this.DOMelements.labels);
- this.redraw();
- return true;
- }
- else {
- DOMutil.cleanupElements(this.DOMelements.lines);
- DOMutil.cleanupElements(this.DOMelements.labels);
- return false;
+ 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._onUpdate();
};
- DataAxis.prototype.convertValue = function (value) {
- var invertedValue = this.valueAtZero - value;
- var convertedValue = invertedValue * this.conversionFactor;
- return convertedValue;
- };
/**
- * Create a label for the axis at position x
+ * Update the datapoints
+ * @param [ids]
* @private
- * @param y
- * @param text
- * @param orientation
- * @param className
- * @param characterHeight
*/
- DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
- // reuse redundant label
- var label = DOMutil.getDOMElement('div',this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();
- label.className = className;
- label.innerHTML = text;
- if (orientation == 'left') {
- label.style.left = '-' + this.options.labelOffsetX + 'px';
- label.style.textAlign = "right";
- }
- else {
- label.style.right = '-' + this.options.labelOffsetX + 'px';
- label.style.textAlign = "left";
+ LineGraph.prototype._onUpdate = function(ids) {
+ this._updateUngrouped();
+ this._updateAllGroupData();
+ this._updateGraph();
+ this.redraw();
+ };
+ LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
+ LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
+ LineGraph.prototype._onUpdateGroups = function (groupIds) {
+ for (var i = 0; i < groupIds.length; i++) {
+ var group = this.groupsData.get(groupIds[i]);
+ this._updateGroup(group, groupIds[i]);
}
- label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
-
- text += '';
+ this._updateGraph();
+ this.redraw();
+ };
+ LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
- var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
- if (this.maxLabelSize < text.length * largestWidth) {
- this.maxLabelSize = text.length * largestWidth;
+ LineGraph.prototype._onRemoveGroups = function (groupIds) {
+ for (var i = 0; i < groupIds.length; i++) {
+ if (!this.groups.hasOwnProperty(groupIds[i])) {
+ if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
+ this.yAxisRight.removeGroup(groupIds[i]);
+ this.legendRight.removeGroup(groupIds[i]);
+ this.legendRight.redraw();
+ }
+ else {
+ this.yAxisLeft.removeGroup(groupIds[i]);
+ this.legendLeft.removeGroup(groupIds[i]);
+ this.legendLeft.redraw();
+ }
+ delete this.groups[groupIds[i]];
+ }
}
+ this._updateUngrouped();
+ this._updateGraph();
+ this.redraw();
};
/**
- * Create a minor line for the axis at position y
- * @param y
- * @param orientation
- * @param className
- * @param offset
- * @param width
+ * update a group object
+ *
+ * @param group
+ * @param groupId
+ * @private
*/
- DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
- if (this.master == true) {
- var line = DOMutil.getDOMElement('div',this.DOMelements.lines, this.dom.lineContainer);//this.dom.redundant.lines.shift();
- line.className = className;
- line.innerHTML = '';
-
- if (orientation == 'left') {
- line.style.left = (this.width - offset) + 'px';
+ LineGraph.prototype._updateGroup = function (group, groupId) {
+ if (!this.groups.hasOwnProperty(groupId)) {
+ this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles);
+ if (this.groups[groupId].options.yAxisOrientation == 'right') {
+ this.yAxisRight.addGroup(groupId, this.groups[groupId]);
+ this.legendRight.addGroup(groupId, this.groups[groupId]);
}
else {
- line.style.right = (this.width - offset) + 'px';
+ this.yAxisLeft.addGroup(groupId, this.groups[groupId]);
+ this.legendLeft.addGroup(groupId, this.groups[groupId]);
}
+ }
+ else {
+ this.groups[groupId].update(group);
+ if (this.groups[groupId].options.yAxisOrientation == 'right') {
+ this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
+ this.legendRight.updateGroup(groupId, this.groups[groupId]);
+ }
+ else {
+ this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
+ this.legendLeft.updateGroup(groupId, this.groups[groupId]);
+ }
+ }
+ this.legendLeft.redraw();
+ this.legendRight.redraw();
+ };
- line.style.width = width + 'px';
- line.style.top = y + 'px';
+ LineGraph.prototype._updateAllGroupData = function () {
+ if (this.itemsData != null) {
+ var groupsContent = {};
+ var groupId;
+ for (groupId in this.groups) {
+ if (this.groups.hasOwnProperty(groupId)) {
+ groupsContent[groupId] = [];
+ }
+ }
+ for (var itemId in this.itemsData._data) {
+ if (this.itemsData._data.hasOwnProperty(itemId)) {
+ var item = this.itemsData._data[itemId];
+ item.x = util.convert(item.x,"Date");
+ groupsContent[item.group].push(item);
+ }
+ }
+ for (groupId in this.groups) {
+ if (this.groups.hasOwnProperty(groupId)) {
+ this.groups[groupId].setItems(groupsContent[groupId]);
+ }
+ }
}
};
+ /**
+ * Create or delete the group holding all ungrouped items. This group is used when
+ * there are no groups specified. This anonymous group is called 'graph'.
+ * @protected
+ */
+ LineGraph.prototype._updateUngrouped = function() {
+ if (this.itemsData && this.itemsData != null) {
+ var ungroupedCounter = 0;
+ for (var itemId in this.itemsData._data) {
+ if (this.itemsData._data.hasOwnProperty(itemId)) {
+ var item = this.itemsData._data[itemId];
+ if (item != undefined) {
+ if (item.hasOwnProperty('group')) {
+ if (item.group === undefined) {
+ item.group = UNGROUPED;
+ }
+ }
+ else {
+ item.group = UNGROUPED;
+ }
+ ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter;
+ }
+ }
+ }
+ if (ungroupedCounter == 0) {
+ delete this.groups[UNGROUPED];
+ this.legendLeft.removeGroup(UNGROUPED);
+ this.legendRight.removeGroup(UNGROUPED);
+ this.yAxisLeft.removeGroup(UNGROUPED);
+ this.yAxisRight.removeGroup(UNGROUPED);
+ }
+ else {
+ var group = {id: UNGROUPED, content: this.options.defaultGroup};
+ this._updateGroup(group, UNGROUPED);
+ }
+ }
+ else {
+ delete this.groups[UNGROUPED];
+ this.legendLeft.removeGroup(UNGROUPED);
+ this.legendRight.removeGroup(UNGROUPED);
+ this.yAxisLeft.removeGroup(UNGROUPED);
+ this.yAxisRight.removeGroup(UNGROUPED);
+ }
+ this.legendLeft.redraw();
+ this.legendRight.redraw();
+ };
/**
- * Determine the size of text on the axis (both major and minor axis).
- * The size is calculated only once and then cached in this.props.
- * @private
+ * Redraw the component, mandatory function
+ * @return {boolean} Returns true if the component is resized
*/
- DataAxis.prototype._calculateCharSize = function () {
- // determine the char width and height on the minor axis
- if (!('minorCharHeight' in this.props)) {
- var textMinor = document.createTextNode('0');
- var measureCharMinor = document.createElement('DIV');
- measureCharMinor.className = 'yAxis minor measure';
- measureCharMinor.appendChild(textMinor);
- this.dom.frame.appendChild(measureCharMinor);
-
- this.props.minorCharHeight = measureCharMinor.clientHeight;
- this.props.minorCharWidth = measureCharMinor.clientWidth;
+ LineGraph.prototype.redraw = function() {
+ var resized = false;
- this.dom.frame.removeChild(measureCharMinor);
+ this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
+ if (this.lastWidth === undefined && this.width || this.lastWidth != this.width) {
+ resized = true;
}
+ // check if this component is resized
+ resized = this._isResized() || resized;
+ // check whether zoomed (in that case we need to re-stack everything)
+ var visibleInterval = this.body.range.end - this.body.range.start;
+ var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.width != this.lastWidth);
+ this.lastVisibleInterval = visibleInterval;
+ this.lastWidth = this.width;
- if (!('majorCharHeight' in this.props)) {
- var textMajor = document.createTextNode('0');
- var measureCharMajor = document.createElement('DIV');
- measureCharMajor.className = 'yAxis major measure';
- measureCharMajor.appendChild(textMajor);
- this.dom.frame.appendChild(measureCharMajor);
-
- this.props.majorCharHeight = measureCharMajor.clientHeight;
- this.props.majorCharWidth = measureCharMajor.clientWidth;
+ // calculate actual size and position
+ this.width = this.dom.frame.offsetWidth;
- this.dom.frame.removeChild(measureCharMajor);
+ // the svg element is three times as big as the width, this allows for fully dragging left and right
+ // without reloading the graph. the controls for this are bound to events in the constructor
+ if (resized == true) {
+ this.svg.style.width = util.option.asSize(3*this.width);
+ this.svg.style.left = util.option.asSize(-this.width);
}
- };
- /**
- * Snap a date to a rounded value.
- * The snap intervals are dependent on the current scale and step.
- * @param {Date} date the date to be snapped.
- * @return {Date} snappedDate
- */
- DataAxis.prototype.snap = function(date) {
- return this.step.snap(date);
- };
+ if (zoomed == true || this.abortedGraphUpdate == true) {
+ this._updateGraph();
+ }
+ else {
+ // move the whole svg while dragging
+ if (this.lastStart != 0) {
+ var offset = this.body.range.start - this.lastStart;
+ var range = this.body.range.end - this.body.range.start;
+ if (this.width != 0) {
+ var rangePerPixelInv = this.width/range;
+ var xOffset = offset * rangePerPixelInv;
+ this.svg.style.left = (-this.width - xOffset) + "px";
+ }
+ }
- module.exports = DataAxis;
+ }
+ this.legendLeft.redraw();
+ this.legendRight.redraw();
-/***/ },
-/* 42 */
-/***/ function(module, exports, __webpack_require__) {
+ return resized;
+ };
/**
- * @constructor DataStep
- * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
- * end data point. The class itself determines the best scale (step size) based on the
- * provided start Date, end Date, and minimumStep.
- *
- * If minimumStep is provided, the step size is chosen as close as possible
- * to the minimumStep but larger than minimumStep. If minimumStep is not
- * provided, the scale is set to 1 DAY.
- * The minimumStep should correspond with the onscreen size of about 6 characters
- *
- * Alternatively, you can set a scale by hand.
- * After creation, you can initialize the class by executing first(). Then you
- * can iterate from the start date to the end date via next(). You can check if
- * the end date is reached with the function hasNext(). After each step, you can
- * retrieve the current date via getCurrent().
- * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
- * days, to years.
- *
- * Version: 1.2
+ * Update and redraw the graph.
*
- * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
- * or new Date(2010, 9, 21, 23, 45, 00)
- * @param {Date} [end] The end date
- * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
*/
- function DataStep(start, end, minimumStep, containerHeight, customRange) {
- // variables
- this.current = 0;
+ LineGraph.prototype._updateGraph = function () {
+ // reset the svg elements
+ DOMutil.prepareElements(this.svgElements);
+ if (this.width != 0 && this.itemsData != null) {
+ var group, i;
+ var preprocessedGroupData = {};
+ var processedGroupData = {};
+ var groupRanges = {};
+ var changeCalled = false;
- this.autoScale = true;
- this.stepIndex = 0;
- this.step = 1;
- this.scale = 1;
+ // getting group Ids
+ var groupIds = [];
+ for (var groupId in this.groups) {
+ if (this.groups.hasOwnProperty(groupId)) {
+ group = this.groups[groupId];
+ if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) {
+ groupIds.push(groupId);
+ }
+ }
+ }
+ if (groupIds.length > 0) {
+ // this is the range of the SVG canvas
+ var minDate = this.body.util.toGlobalTime(- this.body.domProps.root.width);
+ var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
+ var groupsData = {};
+ // fill groups data
+ this._getRelevantData(groupIds, groupsData, minDate, maxDate);
+ // we transform the X coordinates to detect collisions
+ for (i = 0; i < groupIds.length; i++) {
+ preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]);
+ }
+ // now all needed data has been collected we start the processing.
+ this._getYRanges(groupIds, preprocessedGroupData, groupRanges);
- this.marginStart;
- this.marginEnd;
- this.deadSpace = 0;
+ // update the Y axis first, we use this data to draw at the correct Y points
+ // changeCalled is required to clean the SVG on a change emit.
+ changeCalled = this._updateYAxis(groupIds, groupRanges);
+ if (changeCalled == true) {
+ DOMutil.cleanupElements(this.svgElements);
+ this.abortedGraphUpdate = true;
+ this.body.emitter.emit("change");
+ return;
+ }
+ this.abortedGraphUpdate = false;
- this.majorSteps = [1, 2, 5, 10];
- this.minorSteps = [0.25, 0.5, 1, 2];
+ // With the yAxis scaled correctly, use this to get the Y values of the points.
+ for (i = 0; i < groupIds.length; i++) {
+ group = this.groups[groupIds[i]];
+ processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
+ }
- this.setRange(start, end, minimumStep, containerHeight, customRange);
- }
+ // draw the groups
+ for (i = 0; i < groupIds.length; i++) {
+ group = this.groups[groupIds[i]];
+ if (group.options.style == 'line') {
+ this._drawLineGraph(processedGroupData[groupIds[i]], group);
+ }
+ }
+ this._drawBarGraphs(groupIds, processedGroupData);
+ }
+ }
+ // cleanup unused svg elements
+ DOMutil.cleanupElements(this.svgElements);
+ };
- /**
- * Set a new range
- * If minimumStep is provided, the step size is chosen as close as possible
- * to the minimumStep but larger than minimumStep. If minimumStep is not
- * provided, the scale is set to 1 DAY.
- * The minimumStep should correspond with the onscreen size of about 6 characters
- * @param {Number} [start] The start date and time.
- * @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, customRange) {
- this._start = customRange.min === undefined ? start : customRange.min;
- this._end = customRange.max === undefined ? end : customRange.max;
- if (this._start == this._end) {
- this._start -= 0.75;
- this._end += 1;
+ LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) {
+ // first select and preprocess the data from the datasets.
+ // the groups have their preselection of data, we now loop over this data to see
+ // what data we need to draw. Sorted data is much faster.
+ // more optimization is possible by doing the sampling before and using the binary search
+ // to find the end date to determine the increment.
+ var group, i, j, item;
+ if (groupIds.length > 0) {
+ for (i = 0; i < groupIds.length; i++) {
+ group = this.groups[groupIds[i]];
+ groupsData[groupIds[i]] = [];
+ var dataContainer = groupsData[groupIds[i]];
+ // optimization for sorted data
+ if (group.options.sort == true) {
+ var guess = Math.max(0, util.binarySearchGeneric(group.itemsData, minDate, 'x', 'before'));
+ for (j = guess; j < group.itemsData.length; j++) {
+ item = group.itemsData[j];
+ if (item !== undefined) {
+ if (item.x > maxDate) {
+ dataContainer.push(item);
+ break;
+ }
+ else {
+ dataContainer.push(item);
+ }
+ }
+ }
+ }
+ else {
+ for (j = 0; j < group.itemsData.length; j++) {
+ item = group.itemsData[j];
+ if (item !== undefined) {
+ if (item.x > minDate && item.x < maxDate) {
+ dataContainer.push(item);
+ }
+ }
+ }
+ }
+ }
}
- if (this.autoScale) {
- this.setMinimumStep(minimumStep, containerHeight);
+ this._applySampling(groupIds, groupsData);
+ };
+
+ LineGraph.prototype._applySampling = function (groupIds, groupsData) {
+ var group;
+ if (groupIds.length > 0) {
+ for (var i = 0; i < groupIds.length; i++) {
+ group = this.groups[groupIds[i]];
+ if (group.options.sampling == true) {
+ var dataContainer = groupsData[groupIds[i]];
+ if (dataContainer.length > 0) {
+ var increment = 1;
+ var amountOfPoints = dataContainer.length;
+
+ // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
+ // of width changing of the yAxis.
+ var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x);
+ var pointsPerPixel = amountOfPoints / xDistance;
+ increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));
+
+ var sampledData = [];
+ for (var j = 0; j < amountOfPoints; j += increment) {
+ sampledData.push(dataContainer[j]);
+
+ }
+ groupsData[groupIds[i]] = sampledData;
+ }
+ }
+ }
}
- this.setFirst(customRange);
};
- /**
- * Automatically determine the scale that bests fits the provided minimum step
- * @param {Number} [minimumStep] The minimum step size in milliseconds
- */
- DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
- // round to floor
- var size = this._end - this._start;
- var safeSize = size * 1.2;
- var minimumStepValue = minimumStep * (safeSize / containerHeight);
- var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);
+ LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
+ var groupData, group, i,j;
+ var barCombinedDataLeft = [];
+ var barCombinedDataRight = [];
+ var barCombinedData;
+ if (groupIds.length > 0) {
+ for (i = 0; i < groupIds.length; i++) {
+ groupData = groupsData[groupIds[i]];
+ if (groupData.length > 0) {
+ group = this.groups[groupIds[i]];
+ if (group.options.style == 'line' || group.options.barChart.handleOverlap != "stack") {
+ var yMin = groupData[0].y;
+ var yMax = groupData[0].y;
+ for (j = 0; j < groupData.length; j++) {
+ yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
+ yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
+ }
+ groupRanges[groupIds[i]] = {min: yMin, max: yMax, yAxisOrientation: group.options.yAxisOrientation};
+ }
+ else if (group.options.style == 'bar') {
+ if (group.options.yAxisOrientation == 'left') {
+ barCombinedData = barCombinedDataLeft;
+ }
+ else {
+ barCombinedData = barCombinedDataRight;
+ }
- var minorStepIdx = -1;
- var magnitudefactor = Math.pow(10,orderOfMagnitude);
+ groupRanges[groupIds[i]] = {min: 0, max: 0, yAxisOrientation: group.options.yAxisOrientation, ignore: true};
- var start = 0;
- if (orderOfMagnitude < 0) {
- start = orderOfMagnitude;
+ // combine data
+ for (j = 0; j < groupData.length; j++) {
+ barCombinedData.push({
+ x: groupData[j].x,
+ y: groupData[j].y,
+ groupId: groupIds[i]
+ });
+ }
+ }
+ }
+ }
+
+ var intersections;
+ if (barCombinedDataLeft.length > 0) {
+ // sort by time and by group
+ barCombinedDataLeft.sort(function (a, b) {
+ if (a.x == b.x) {
+ return a.groupId - b.groupId;
+ } else {
+ return a.x - b.x;
+ }
+ });
+ intersections = {};
+ this._getDataIntersections(intersections, barCombinedDataLeft);
+ groupRanges["__barchartLeft"] = this._getStackedBarYRange(intersections, barCombinedDataLeft);
+ groupRanges["__barchartLeft"].yAxisOrientation = "left";
+ groupIds.push("__barchartLeft");
+ }
+ if (barCombinedDataRight.length > 0) {
+ // sort by time and by group
+ barCombinedDataRight.sort(function (a, b) {
+ if (a.x == b.x) {
+ return a.groupId - b.groupId;
+ } else {
+ return a.x - b.x;
+ }
+ });
+ intersections = {};
+ this._getDataIntersections(intersections, barCombinedDataRight);
+ groupRanges["__barchartRight"] = this._getStackedBarYRange(intersections, barCombinedDataRight);
+ groupRanges["__barchartRight"].yAxisOrientation = "right";
+ groupIds.push("__barchartRight");
+ }
}
+ };
- var solutionFound = false;
- for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
- magnitudefactor = Math.pow(10,i);
- for (var j = 0; j < this.minorSteps.length; j++) {
- var stepSize = magnitudefactor * this.minorSteps[j];
- if (stepSize >= minimumStepValue) {
- solutionFound = true;
- minorStepIdx = j;
- break;
+ LineGraph.prototype._getStackedBarYRange = function (intersections, combinedData) {
+ var key;
+ var yMin = combinedData[0].y;
+ var yMax = combinedData[0].y;
+ for (var i = 0; i < combinedData.length; i++) {
+ key = combinedData[i].x;
+ if (intersections[key] === undefined) {
+ yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin;
+ yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax;
+ }
+ else {
+ intersections[key].accumulated += combinedData[i].y;
+ }
+ }
+ for (var xpos in intersections) {
+ if (intersections.hasOwnProperty(xpos)) {
+ yMin = yMin > intersections[xpos].accumulated ? intersections[xpos].accumulated : yMin;
+ yMax = yMax < intersections[xpos].accumulated ? intersections[xpos].accumulated : yMax;
+ }
+ }
+
+ return {min: yMin, max: yMax};
+ };
+
+
+ /**
+ * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
+ * @param {Array} groupIds
+ * @param {Object} groupRanges
+ * @private
+ */
+ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
+ var changeCalled = false;
+ var yAxisLeftUsed = false;
+ var yAxisRightUsed = false;
+ var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
+ // if groups are present
+ if (groupIds.length > 0) {
+ for (var i = 0; i < groupIds.length; i++) {
+ if (groupRanges.hasOwnProperty(groupIds[i])) {
+ if (groupRanges[groupIds[i]].ignore !== true) {
+ minVal = groupRanges[groupIds[i]].min;
+ maxVal = groupRanges[groupIds[i]].max;
+
+ if (groupRanges[groupIds[i]].yAxisOrientation == 'left') {
+ yAxisLeftUsed = true;
+ minLeft = minLeft > minVal ? minVal : minLeft;
+ maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
+ }
+ else {
+ yAxisRightUsed = true;
+ minRight = minRight > minVal ? minVal : minRight;
+ maxRight = maxRight < maxVal ? maxVal : maxRight;
+ }
+ }
}
}
- if (solutionFound == true) {
- break;
+
+ if (yAxisLeftUsed == true) {
+ this.yAxisLeft.setRange(minLeft, maxLeft);
+ }
+ if (yAxisRightUsed == true) {
+ this.yAxisRight.setRange(minRight, maxRight);
}
}
- this.stepIndex = minorStepIdx;
- this.scale = magnitudefactor;
- this.step = magnitudefactor * this.minorSteps[minorStepIdx];
- };
-
+ changeCalled = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || changeCalled;
+ changeCalled = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || changeCalled;
- /**
- * Round the current date to the first minor date value
- * This must be executed once when the current date is set to start Date
- */
- DataStep.prototype.setFirst = function(customRange) {
- if (customRange === undefined) {
- customRange = {};
+ if (yAxisRightUsed == true && yAxisLeftUsed == true) {
+ this.yAxisLeft.drawIcons = true;
+ this.yAxisRight.drawIcons = true;
+ }
+ else {
+ this.yAxisLeft.drawIcons = false;
+ this.yAxisRight.drawIcons = false;
}
- var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min;
- var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max;
-
- this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max;
- this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min;
- this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart;
- this.marginRange = this.marginEnd - this.marginStart;
- this.current = this.marginEnd;
+ this.yAxisRight.master = !yAxisLeftUsed;
- };
+ if (this.yAxisRight.master == false) {
+ if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;}
+ else {this.yAxisLeft.lineOffset = 0;}
- DataStep.prototype.roundToMinor = function(value) {
- var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
- if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
- return rounded + (this.scale * this.minorSteps[this.stepIndex]);
+ changeCalled = this.yAxisLeft.redraw() || changeCalled;
+ this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
+ changeCalled = this.yAxisRight.redraw() || changeCalled;
}
else {
- return rounded;
+ changeCalled = this.yAxisRight.redraw() || changeCalled;
}
- }
+ // clean the accumulated lists
+ if (groupIds.indexOf("__barchartLeft") != -1) {
+ groupIds.splice(groupIds.indexOf("__barchartLeft"),1);
+ }
+ if (groupIds.indexOf("__barchartRight") != -1) {
+ groupIds.splice(groupIds.indexOf("__barchartRight"),1);
+ }
- /**
- * Check if the there is a next step
- * @return {boolean} true if the current date has not passed the end date
- */
- DataStep.prototype.hasNext = function () {
- return (this.current >= this.marginStart);
+ return changeCalled;
};
/**
- * Do the next step
+ * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function
+ *
+ * @param {boolean} axisUsed
+ * @returns {boolean}
+ * @private
+ * @param axis
*/
- DataStep.prototype.next = function() {
- var prev = this.current;
- this.current -= this.step;
-
- // safety mechanism: if current time is still unchanged, move to the end
- if (this.current == prev) {
- this.current = this._end;
+ LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
+ var changed = false;
+ if (axisUsed == false) {
+ if (axis.dom.frame.parentNode) {
+ axis.hide();
+ changed = true;
+ }
}
- };
-
- /**
- * Do the next step
- */
- DataStep.prototype.previous = function() {
- this.current += this.step;
- this.marginEnd += this.step;
- this.marginRange = this.marginEnd - this.marginStart;
+ else {
+ if (!axis.dom.frame.parentNode) {
+ axis.show();
+ changed = true;
+ }
+ }
+ return changed;
};
-
/**
- * Get the current datetime
- * @return {String} current The current date
+ * draw a bar graph
+ *
+ * @param groupIds
+ * @param processedGroupData
*/
- DataStep.prototype.getCurrent = function() {
- var toPrecision = '' + Number(this.current).toPrecision(5);
- if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) {
- for (var i = toPrecision.length-1; i > 0; i--) {
- if (toPrecision[i] == "0") {
- toPrecision = toPrecision.slice(0,i);
- }
- else if (toPrecision[i] == "." || toPrecision[i] == ",") {
- toPrecision = toPrecision.slice(0,i);
- break;
- }
- else{
- break;
+ LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) {
+ var combinedData = [];
+ var intersections = {};
+ var coreDistance;
+ var key, drawData;
+ var group;
+ var i,j;
+ var barPoints = 0;
+
+ // combine all barchart data
+ for (i = 0; i < groupIds.length; i++) {
+ group = this.groups[groupIds[i]];
+ if (group.options.style == 'bar') {
+ if (group.visible == true && (this.options.groups.visibility[groupIds[i]] === undefined || this.options.groups.visibility[groupIds[i]] == true)) {
+ for (j = 0; j < processedGroupData[groupIds[i]].length; j++) {
+ combinedData.push({
+ x: processedGroupData[groupIds[i]][j].x,
+ y: processedGroupData[groupIds[i]][j].y,
+ groupId: groupIds[i]
+ });
+ barPoints += 1;
+ }
}
}
}
- return toPrecision;
- };
+ if (barPoints == 0) {return;}
+
+ // sort by time and by group
+ combinedData.sort(function (a, b) {
+ if (a.x == b.x) {
+ return a.groupId - b.groupId;
+ } else {
+ return a.x - b.x;
+ }
+ });
+ // get intersections
+ this._getDataIntersections(intersections, combinedData);
+ // plot barchart
+ for (i = 0; i < combinedData.length; i++) {
+ group = this.groups[combinedData[i].groupId];
+ var minWidth = 0.1 * group.options.barChart.width;
- /**
- * Snap a date to a rounded value.
- * The snap intervals are dependent on the current scale and step.
- * @param {Date} date the date to be snapped.
- * @return {Date} snappedDate
- */
- DataStep.prototype.snap = function(date) {
+ key = combinedData[i].x;
+ var heightOffset = 0;
+ if (intersections[key] === undefined) {
+ if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);}
+ if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));}
+ drawData = this._getSafeDrawData(coreDistance, group, minWidth);
+ }
+ else {
+ var nextKey = i + (intersections[key].amount - intersections[key].resolved);
+ var prevKey = i - (intersections[key].resolved + 1);
+ if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);}
+ if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));}
+ drawData = this._getSafeDrawData(coreDistance, group, minWidth);
+ intersections[key].resolved += 1;
+ if (group.options.barChart.handleOverlap == 'stack') {
+ heightOffset = intersections[key].accumulated;
+ intersections[key].accumulated += group.zeroPosition - combinedData[i].y;
+ }
+ else if (group.options.barChart.handleOverlap == 'sideBySide') {
+ drawData.width = drawData.width / intersections[key].amount;
+ drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
+ if (group.options.barChart.align == 'left') {drawData.offset -= 0.5*drawData.width;}
+ else if (group.options.barChart.align == 'right') {drawData.offset += 0.5*drawData.width;}
+ }
+ }
+ DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' bar', this.svgElements, this.svg);
+ // draw points
+ if (group.options.drawPoints.enabled == true) {
+ DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, group, this.svgElements, this.svg);
+ }
+ }
};
/**
- * Check if the current value is a major value (for example when the step
- * is DAY, a major value is each first day of the MONTH)
- * @return {boolean} true if current date is major, else false.
+ * Fill the intersections object with counters of how many datapoints share the same x coordinates
+ * @param intersections
+ * @param combinedData
+ * @private
*/
- DataStep.prototype.isMajor = function() {
- return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
+ LineGraph.prototype._getDataIntersections = function (intersections, combinedData) {
+ // get intersections
+ var coreDistance;
+ for (var i = 0; i < combinedData.length; i++) {
+ if (i + 1 < combinedData.length) {
+ coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
+ }
+ if (i > 0) {
+ coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
+ }
+ if (coreDistance == 0) {
+ if (intersections[combinedData[i].x] === undefined) {
+ intersections[combinedData[i].x] = {amount: 0, resolved: 0, accumulated: 0};
+ }
+ intersections[combinedData[i].x].amount += 1;
+ }
+ }
};
- module.exports = DataStep;
-
-
-/***/ },
-/* 43 */
-/***/ function(module, exports, __webpack_require__) {
-
- var util = __webpack_require__(1);
- var DOMutil = __webpack_require__(6);
-
/**
- * @constructor Group
- * @param {Number | String} groupId
- * @param {Object} data
- * @param {ItemSet} itemSet
+ * Get the width and offset for bargraphs based on the coredistance between datapoints
+ *
+ * @param coreDistance
+ * @param group
+ * @param minWidth
+ * @returns {{width: Number, offset: Number}}
+ * @private
*/
- function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
- this.id = groupId;
- var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
- this.options = util.selectiveBridgeObject(fields,options);
- this.usingDefaultStyle = group.className === undefined;
- this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
- this.zeroPosition = 0;
- this.update(group);
- if (this.usingDefaultStyle == true) {
- this.groupsUsingDefaultStyles[0] += 1;
- }
- this.itemsData = [];
- this.visible = group.visible === undefined ? true : group.visible;
- }
+ LineGraph.prototype._getSafeDrawData = function (coreDistance, group, minWidth) {
+ var width, offset;
+ if (coreDistance < group.options.barChart.width && coreDistance > 0) {
+ width = coreDistance < minWidth ? minWidth : coreDistance;
- GraphGroup.prototype.setItems = function(items) {
- if (items != null) {
- this.itemsData = items;
- if (this.options.sort == true) {
- this.itemsData.sort(function (a,b) {return a.x - b.x;})
+ offset = 0; // recalculate offset with the new width;
+ if (group.options.barChart.align == 'left') {
+ offset -= 0.5 * coreDistance;
+ }
+ else if (group.options.barChart.align == 'right') {
+ offset += 0.5 * coreDistance;
}
}
else {
- this.itemsData = [];
- }
- };
-
- GraphGroup.prototype.setZeroPosition = function(pos) {
- this.zeroPosition = pos;
- };
-
- GraphGroup.prototype.setOptions = function(options) {
- if (options !== undefined) {
- var fields = ['sampling','style','sort','yAxisOrientation','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');
-
- if (options.catmullRom) {
- if (typeof options.catmullRom == 'object') {
- if (options.catmullRom.parametrization) {
- if (options.catmullRom.parametrization == 'uniform') {
- this.options.catmullRom.alpha = 0;
- }
- else if (options.catmullRom.parametrization == 'chordal') {
- this.options.catmullRom.alpha = 1.0;
- }
- else {
- this.options.catmullRom.parametrization = 'centripetal';
- this.options.catmullRom.alpha = 0.5;
- }
- }
- }
+ // default settings
+ width = group.options.barChart.width;
+ offset = 0;
+ if (group.options.barChart.align == 'left') {
+ offset -= 0.5 * group.options.barChart.width;
+ }
+ else if (group.options.barChart.align == 'right') {
+ offset += 0.5 * group.options.barChart.width;
}
}
- };
- GraphGroup.prototype.update = function(group) {
- this.group = group;
- this.content = group.content || 'graph';
- this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10;
- this.visible = group.visible === undefined ? true : group.visible;
- this.setOptions(group.options);
+ return {width: width, offset: offset};
};
- GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
- var fillHeight = iconHeight * 0.5;
- var path, fillPath;
- var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer);
- outline.setAttributeNS(null, "x", x);
- outline.setAttributeNS(null, "y", y - fillHeight);
- outline.setAttributeNS(null, "width", iconWidth);
- outline.setAttributeNS(null, "height", 2*fillHeight);
- outline.setAttributeNS(null, "class", "outline");
+ /**
+ * draw a line graph
+ *
+ * @param dataset
+ * @param group
+ */
+ LineGraph.prototype._drawLineGraph = function (dataset, group) {
+ if (dataset != null) {
+ if (dataset.length > 0) {
+ var path, d;
+ var svgHeight = Number(this.svg.style.height.replace("px",""));
+ path = DOMutil.getSVGElement('path', this.svgElements, this.svg);
+ path.setAttributeNS(null, "class", group.className);
- if (this.options.style == 'line') {
- path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
- path.setAttributeNS(null, "class", this.className);
- path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
- if (this.options.shaded.enabled == true) {
- fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
- if (this.options.shaded.orientation == 'top') {
- fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
- "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
+ // construct path from dataset
+ if (group.options.catmullRom.enabled == true) {
+ d = this._catmullRom(dataset, group);
}
else {
- fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
- "L"+x+"," + (y + fillHeight) + " " +
- "L"+ (x + iconWidth) + "," + (y + fillHeight) +
- "L"+ (x + iconWidth) + ","+y);
+ d = this._linear(dataset);
}
- fillPath.setAttributeNS(null, "class", this.className + " iconFill");
- }
-
- if (this.options.drawPoints.enabled == true) {
- DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer);
- }
- }
- else {
- var barWidth = Math.round(0.3 * iconWidth);
- var bar1Height = Math.round(0.4 * iconHeight);
- var bar2Height = Math.round(0.75 * iconHeight);
- var offset = Math.round((iconWidth - (2 * barWidth))/3);
+ // append with points for fill and finalize the path
+ if (group.options.shaded.enabled == true) {
+ var fillPath = DOMutil.getSVGElement('path',this.svgElements, this.svg);
+ var dFill;
+ if (group.options.shaded.orientation == 'top') {
+ dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0;
+ }
+ else {
+ dFill = "M" + dataset[0].x + "," + svgHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + svgHeight;
+ }
+ fillPath.setAttributeNS(null, "class", group.className + " fill");
+ fillPath.setAttributeNS(null, "d", dFill);
+ }
+ // copy properties to path for drawing.
+ path.setAttributeNS(null, "d", "M" + d);
- DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer);
- DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer);
+ // draw points
+ if (group.options.drawPoints.enabled == true) {
+ this._drawPoints(dataset, group, this.svgElements, this.svg);
+ }
+ }
}
};
/**
+ * draw the data points
*
- * @param iconWidth
- * @param iconHeight
- * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}}
+ * @param {Array} dataset
+ * @param {Object} JSONcontainer
+ * @param {Object} svg | SVG DOM element
+ * @param {GraphGroup} group
+ * @param {Number} [offset]
*/
- GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) {
- var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
- this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight);
- return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation};
- }
-
- module.exports = GraphGroup;
-
+ LineGraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg, offset) {
+ if (offset === undefined) {offset = 0;}
+ for (var i = 0; i < dataset.length; i++) {
+ DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, JSONcontainer, svg);
+ }
+ };
-/***/ },
-/* 44 */
-/***/ function(module, exports, __webpack_require__) {
- var util = __webpack_require__(1);
- var DOMutil = __webpack_require__(6);
- var Component = __webpack_require__(22);
/**
- * Legend for Graph2d
+ * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
+ * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
+ * the yAxis.
+ *
+ * @param datapoints
+ * @returns {Array}
+ * @private
*/
- function Legend(body, options, side, linegraphOptions) {
- this.body = body;
- this.defaultOptions = {
- enabled: true,
- icons: true,
- iconSize: 20,
- iconSpacing: 6,
- left: {
- visible: true,
- position: 'top-left' // top/bottom - left,center,right
- },
- right: {
- visible: true,
- position: 'top-left' // top/bottom - left,center,right
- }
- }
- this.side = side;
- this.options = util.extend({},this.defaultOptions);
- this.linegraphOptions = linegraphOptions;
+ LineGraph.prototype._convertXcoordinates = function (datapoints) {
+ var extractedData = [];
+ var xValue, yValue;
+ var toScreen = this.body.util.toScreen;
- this.svgElements = {};
- this.dom = {};
- this.groups = {};
- this.amountOfGroups = 0;
- this._create();
+ for (var i = 0; i < datapoints.length; i++) {
+ xValue = toScreen(datapoints[i].x) + this.width;
+ yValue = datapoints[i].y;
+ extractedData.push({x: xValue, y: yValue});
+ }
- this.setOptions(options);
- }
+ return extractedData;
+ };
- Legend.prototype = new Component();
- Legend.prototype.addGroup = function(label, graphOptions) {
- if (!this.groups.hasOwnProperty(label)) {
- this.groups[label] = graphOptions;
+ /**
+ * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
+ * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
+ * the yAxis.
+ *
+ * @param datapoints
+ * @returns {Array}
+ * @private
+ */
+ LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
+ var extractedData = [];
+ var xValue, yValue;
+ var toScreen = this.body.util.toScreen;
+ var axis = this.yAxisLeft;
+ var svgHeight = Number(this.svg.style.height.replace("px",""));
+ if (group.options.yAxisOrientation == 'right') {
+ axis = this.yAxisRight;
}
- this.amountOfGroups += 1;
- };
-
- Legend.prototype.updateGroup = function(label, graphOptions) {
- this.groups[label] = graphOptions;
- };
- Legend.prototype.removeGroup = function(label) {
- if (this.groups.hasOwnProperty(label)) {
- delete this.groups[label];
- this.amountOfGroups -= 1;
+ for (var i = 0; i < datapoints.length; i++) {
+ xValue = toScreen(datapoints[i].x) + this.width;
+ yValue = Math.round(axis.convertValue(datapoints[i].y));
+ extractedData.push({x: xValue, y: yValue});
}
- };
-
- Legend.prototype._create = function() {
- this.dom.frame = document.createElement('div');
- this.dom.frame.className = 'legend';
- this.dom.frame.style.position = "absolute";
- this.dom.frame.style.top = "10px";
- this.dom.frame.style.display = "block";
-
- this.dom.textArea = document.createElement('div');
- this.dom.textArea.className = 'legendText';
- this.dom.textArea.style.position = "relative";
- this.dom.textArea.style.top = "0px";
- this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
- this.svg.style.position = 'absolute';
- this.svg.style.top = 0 +'px';
- this.svg.style.width = this.options.iconSize + 5 + 'px';
+ group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
- this.dom.frame.appendChild(this.svg);
- this.dom.frame.appendChild(this.dom.textArea);
+ return extractedData;
};
/**
- * Hide the component from the DOM
+ * This uses an uniform parametrization of the CatmullRom algorithm:
+ * "On the Parameterization of Catmull-Rom Curves" by Cem Yuksel et al.
+ * @param data
+ * @returns {string}
+ * @private
*/
- Legend.prototype.hide = function() {
- // remove the frame containing the items
- if (this.dom.frame.parentNode) {
- this.dom.frame.parentNode.removeChild(this.dom.frame);
+ LineGraph.prototype._catmullRomUniform = function(data) {
+ // catmull rom
+ var p0, p1, p2, p3, bp1, bp2;
+ var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
+ var normalization = 1/6;
+ var length = data.length;
+ for (var i = 0; i < length - 1; i++) {
+
+ p0 = (i == 0) ? data[0] : data[i-1];
+ p1 = data[i];
+ p2 = data[i+1];
+ p3 = (i + 2 < length) ? data[i+2] : p2;
+
+
+ // Catmull-Rom to Cubic Bezier conversion matrix
+ // 0 1 0 0
+ // -1/6 1 1/6 0
+ // 0 1/6 1 -1/6
+ // 0 0 1 0
+
+ // bp0 = { x: p1.x, y: p1.y };
+ bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
+ bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
+ // bp0 = { x: p2.x, y: p2.y };
+
+ d += "C" +
+ bp1.x + "," +
+ bp1.y + " " +
+ bp2.x + "," +
+ bp2.y + " " +
+ p2.x + "," +
+ p2.y + " ";
}
+
+ return d;
};
/**
- * Show the component in the DOM (when not already visible).
- * @return {Boolean} changed
+ * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm.
+ * By default, the centripetal parameterization is used because this gives the nicest results.
+ * These parameterizations are relatively heavy because the distance between 4 points have to be calculated.
+ *
+ * One optimization can be used to reuse distances since this is a sliding window approach.
+ * @param data
+ * @returns {string}
+ * @private
*/
- Legend.prototype.show = function() {
- // show frame containing the items
- if (!this.dom.frame.parentNode) {
- this.body.dom.center.appendChild(this.dom.frame);
+ LineGraph.prototype._catmullRom = function(data, group) {
+ var alpha = group.options.catmullRom.alpha;
+ if (alpha == 0 || alpha === undefined) {
+ return this._catmullRomUniform(data);
}
- };
+ else {
+ var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
+ var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
+ var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
+ var length = data.length;
+ for (var i = 0; i < length - 1; i++) {
- Legend.prototype.setOptions = function(options) {
- var fields = ['enabled','orientation','icons','left','right'];
- util.selectiveDeepExtend(fields, this.options, options);
- };
+ p0 = (i == 0) ? data[0] : data[i-1];
+ p1 = data[i];
+ p2 = data[i+1];
+ p3 = (i + 2 < length) ? data[i+2] : p2;
- Legend.prototype.redraw = function() {
- var activeGroups = 0;
- for (var groupId in this.groups) {
- if (this.groups.hasOwnProperty(groupId)) {
- if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
- activeGroups++;
- }
- }
- }
+ d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
+ d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
+ d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
- if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) {
- this.hide();
- }
- else {
- this.show();
- if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') {
- this.dom.frame.style.left = '4px';
- this.dom.frame.style.textAlign = "left";
- this.dom.textArea.style.textAlign = "left";
- this.dom.textArea.style.left = (this.options.iconSize + 15) + 'px';
- this.dom.textArea.style.right = '';
- this.svg.style.left = 0 +'px';
- this.svg.style.right = '';
- }
- else {
- this.dom.frame.style.right = '4px';
- this.dom.frame.style.textAlign = "right";
- this.dom.textArea.style.textAlign = "right";
- this.dom.textArea.style.right = (this.options.iconSize + 15) + 'px';
- this.dom.textArea.style.left = '';
- this.svg.style.right = 0 +'px';
- this.svg.style.left = '';
- }
+ // Catmull-Rom to Cubic Bezier conversion matrix
+ //
+ // A = 2d1^2a + 3d1^a * d2^a + d3^2a
+ // B = 2d3^2a + 3d3^a * d2^a + d2^2a
+ //
+ // [ 0 1 0 0 ]
+ // [ -d2^2a/N A/N d1^2a/N 0 ]
+ // [ 0 d3^2a/M B/M -d2^2a/M ]
+ // [ 0 0 1 0 ]
- if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') {
- this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
- this.dom.frame.style.bottom = '';
- }
- else {
- this.dom.frame.style.bottom = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
- this.dom.frame.style.top = '';
- }
+ // [ 0 1 0 0 ]
+ // [ -d2pow2a/N A/N d1pow2a/N 0 ]
+ // [ 0 d3pow2a/M B/M -d2pow2a/M ]
+ // [ 0 0 1 0 ]
- if (this.options.icons == false) {
- this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px';
- this.dom.textArea.style.right = '';
- this.dom.textArea.style.left = '';
- this.svg.style.width = '0px';
- }
- else {
- this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'
- this.drawLegendIcons();
- }
+ d3powA = Math.pow(d3, alpha);
+ d3pow2A = Math.pow(d3,2*alpha);
+ d2powA = Math.pow(d2, alpha);
+ d2pow2A = Math.pow(d2,2*alpha);
+ d1powA = Math.pow(d1, alpha);
+ d1pow2A = Math.pow(d1,2*alpha);
- var content = '';
- for (var groupId in this.groups) {
- if (this.groups.hasOwnProperty(groupId)) {
- if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
- content += this.groups[groupId].content + '
';
- }
- }
- }
- this.dom.textArea.innerHTML = content;
- this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px';
- }
- };
+ A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
+ B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
+ N = 3*d1powA * (d1powA + d2powA);
+ if (N > 0) {N = 1 / N;}
+ M = 3*d3powA * (d3powA + d2powA);
+ if (M > 0) {M = 1 / M;}
- Legend.prototype.drawLegendIcons = function() {
- if (this.dom.frame.parentNode) {
- DOMutil.prepareElements(this.svgElements);
- var padding = window.getComputedStyle(this.dom.frame).paddingTop;
- var iconOffset = Number(padding.replace('px',''));
- var x = iconOffset;
- var iconWidth = this.options.iconSize;
- var iconHeight = 0.75 * this.options.iconSize;
- var y = iconOffset + 0.5 * iconHeight + 3;
+ bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
+ y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
- this.svg.style.width = iconWidth + 5 + iconOffset + 'px';
+ bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
+ y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
- for (var groupId in this.groups) {
- if (this.groups.hasOwnProperty(groupId)) {
- if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
- this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
- y += iconHeight + this.options.iconSpacing;
- }
- }
+ if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
+ if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
+ d += "C" +
+ bp1.x + "," +
+ bp1.y + " " +
+ bp2.x + "," +
+ bp2.y + " " +
+ p2.x + "," +
+ p2.y + " ";
}
- DOMutil.cleanupElements(this.svgElements);
+ return d;
}
};
- module.exports = Legend;
+ /**
+ * this generates the SVG path for a linear drawing between datapoints.
+ * @param data
+ * @returns {string}
+ * @private
+ */
+ LineGraph.prototype._linear = function(data) {
+ // linear
+ var d = "";
+ for (var i = 0; i < data.length; i++) {
+ if (i == 0) {
+ d += data[i].x + "," + data[i].y;
+ }
+ else {
+ d += " " + data[i].x + "," + data[i].y;
+ }
+ }
+ return d;
+ };
+
+ module.exports = LineGraph;
/***/ },
diff --git a/lib/timeline/Graph2d.js b/lib/timeline/Graph2d.js
index 5874e5b7..3cf045e6 100644
--- a/lib/timeline/Graph2d.js
+++ b/lib/timeline/Graph2d.js
@@ -18,7 +18,14 @@ var LineGraph = require('./component/LineGraph');
* @constructor
* @extends Core
*/
-function Graph2d (container, items, options, groups) {
+function Graph2d (container, items, groups, options) {
+ // if the third element is options, the forth is groups (optionally);
+ if (!(groups instanceof Array || groups instanceof vis.DataSet) && groups instanceof Object) {
+ var forthArgument = options;
+ options = groups;
+ groups = forthArgument;
+ }
+
var me = this;
this.defaultOptions = {
start: null,
diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js
index cd6f1fd5..2707bf29 100644
--- a/lib/timeline/Timeline.js
+++ b/lib/timeline/Timeline.js
@@ -18,11 +18,18 @@ var ItemSet = require('./component/ItemSet');
* @constructor
* @extends Core
*/
-function Timeline (container, items, options) {
+function Timeline (container, items, groups, options) {
if (!(this instanceof Timeline)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
+ // if the third element is options, the forth is groups (optionally);
+ if (!(groups instanceof Array || groups instanceof vis.DataSet) && groups instanceof Object) {
+ var forthArgument = options;
+ options = groups;
+ groups = forthArgument;
+ }
+
var me = this;
this.defaultOptions = {
start: null,
@@ -92,6 +99,11 @@ function Timeline (container, items, options) {
this.setOptions(options);
}
+ // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
+ if (groups) {
+ this.setGroups(groups);
+ }
+
// create itemset
if (items) {
this.setItems(items);