Browse Source

Fixed #1403: Graph2d change yAxisOrientation,

Cleanup of linegraph's event handling
Fixed #1557: Make sure we don't default to scientific notation.
Couple of minor fixes in group counts and ranges.
codeClimate
Ludo Stellingwerff 8 years ago
parent
commit
bbf04d3489
5 changed files with 130 additions and 107 deletions
  1. +21
    -9
      lib/timeline/Core.js
  2. +4
    -0
      lib/timeline/DataStep.js
  3. +3
    -4
      lib/timeline/Graph2d.js
  4. +5
    -2
      lib/timeline/component/DataAxis.js
  5. +97
    -92
      lib/timeline/component/LineGraph.js

+ 21
- 9
lib/timeline/Core.js View File

@ -91,7 +91,9 @@ Core.prototype._create = function (container) {
this.dom.rightContainer.appendChild(this.dom.shadowBottomRight); this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
this.on('rangechange', function () { this.on('rangechange', function () {
this._redraw(); // this allows overriding the _redraw method
if (this.initialDrawDone) {
this._redraw(); // this allows overriding the _redraw method
}
}.bind(this)); }.bind(this));
this.on('touch', this._onTouch.bind(this)); this.on('touch', this._onTouch.bind(this));
this.on('pan', this._onDrag.bind(this)); this.on('pan', this._onDrag.bind(this));
@ -181,6 +183,7 @@ Core.prototype._create = function (container) {
this.touch = {}; this.touch = {};
this.redrawCount = 0; this.redrawCount = 0;
this.initialDrawDone = false;
// attach the root panel to the provided container // attach the root panel to the provided container
if (!container) throw new Error('No container provided'); if (!container) throw new Error('No container provided');
@ -317,11 +320,12 @@ Core.prototype.setOptions = function (options) {
// override redraw with a throttled version // override redraw with a throttled version
if (!this._origRedraw) { if (!this._origRedraw) {
this._origRedraw = this._redraw.bind(this); this._origRedraw = this._redraw.bind(this);
this._redraw = util.throttle(this._origRedraw, this.options.throttleRedraw);
} else {
// Not the initial run: redraw everything
this._redraw();
} }
this._redraw = util.throttle(this._origRedraw, this.options.throttleRedraw);
// redraw everything
this._redraw();
}; };
/** /**
@ -621,6 +625,7 @@ Core.prototype.redraw = function() {
* @protected * @protected
*/ */
Core.prototype._redraw = function() { Core.prototype._redraw = function() {
this.redrawCount++;
var resized = false; var resized = false;
var options = this.options; var options = this.options;
var props = this.props; var props = this.props;
@ -766,18 +771,19 @@ Core.prototype._redraw = function() {
this.components.forEach(function (component) { this.components.forEach(function (component) {
resized = component.redraw() || resized; resized = component.redraw() || resized;
}); });
var MAX_REDRAW = 5;
if (resized) { if (resized) {
// keep repainting until all sizes are settled
var MAX_REDRAWS = 3; // maximum number of consecutive redraws
if (this.redrawCount < MAX_REDRAWS) {
this.redrawCount++;
this._redraw();
if (this.redrawCount < MAX_REDRAW) {
this.body.emitter.emit('change');
return;
} }
else { else {
console.log('WARNING: infinite loop in redraw?'); console.log('WARNING: infinite loop in redraw?');
} }
} else {
this.redrawCount = 0; this.redrawCount = 0;
} }
this.initialDrawDone = true;
}; };
// TODO: deprecated since version 1.1.0, remove some day // TODO: deprecated since version 1.1.0, remove some day
@ -915,6 +921,12 @@ Core.prototype._startAutoResize = function () {
// add event listener to window resize // add event listener to window resize
util.addEventListener(window, 'resize', this._onResize); util.addEventListener(window, 'resize', this._onResize);
//Prevent initial unnecessary redraw
if (me.dom.root) {
me.props.lastWidth = me.dom.root.offsetWidth;
me.props.lastHeight = me.dom.root.offsetHeight;
}
this.watchTimer = setInterval(this._onResize, 1000); this.watchTimer = setInterval(this._onResize, 1000);
}; };

+ 4
- 0
lib/timeline/DataStep.js View File

@ -59,6 +59,10 @@ function DataStep(start, end, minimumStep, containerHeight, customRange, formatt
* @param {Number} [minimumStep] Optional. Minimum step size in milliseconds * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
*/ */
DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) { DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) {
if (customRange === undefined) {
customRange = {};
}
this._start = customRange.min === undefined ? start : customRange.min; this._start = customRange.min === undefined ? start : customRange.min;
this._end = customRange.max === undefined ? end : customRange.max; this._end = customRange.max === undefined ? end : customRange.max;
if (this._start === this._end) { if (this._start === this._end) {

+ 3
- 4
lib/timeline/Graph2d.js View File

@ -93,11 +93,13 @@ function Graph2d (container, items, groups, options) {
// item set // item set
this.linegraph = new LineGraph(this.body); this.linegraph = new LineGraph(this.body);
this.components.push(this.linegraph); this.components.push(this.linegraph);
this.itemsData = null; // DataSet this.itemsData = null; // DataSet
this.groupsData = null; // DataSet this.groupsData = null; // DataSet
this.on('tap', function (event) { this.on('tap', function (event) {
me.emit('click', me.getEventProperties(event)) me.emit('click', me.getEventProperties(event))
}); });
@ -122,9 +124,7 @@ function Graph2d (container, items, groups, options) {
if (items) { if (items) {
this.setItems(items); this.setItems(items);
} }
else {
this._redraw();
}
this._redraw();
} }
// Extend the functionality from Core // Extend the functionality from Core
@ -173,7 +173,6 @@ Graph2d.prototype.setItems = function(items) {
if (this.options.start != undefined || this.options.end != undefined) { if (this.options.start != undefined || this.options.end != undefined) {
var start = this.options.start != undefined ? this.options.start : null; var start = this.options.start != undefined ? this.options.start : null;
var end = this.options.end != undefined ? this.options.end : null; var end = this.options.end != undefined ? this.options.end : null;
this.setWindow(start, end, {animation: false}); this.setWindow(start, end, {animation: false});
} }
else { else {

+ 5
- 2
lib/timeline/component/DataAxis.js View File

@ -30,12 +30,12 @@ function DataAxis (body, options, svg, linegraphOptions) {
alignZeros: true, alignZeros: true,
left:{ left:{
range: {min:undefined,max:undefined}, range: {min:undefined,max:undefined},
format: function (value) {return ''+value.toPrecision(3);},
format: function (value) {return ''+Number.parseFloat(value.toPrecision(3));},
title: {text:undefined,style:undefined} title: {text:undefined,style:undefined}
}, },
right:{ right:{
range: {min:undefined,max:undefined}, range: {min:undefined,max:undefined},
format: function (value) {return ''+value.toPrecision(3);},
format: function (value) {return ''+Number.parseFloat(value.toPrecision(3));},
title: {text:undefined,style:undefined} title: {text:undefined,style:undefined}
} }
}; };
@ -96,6 +96,9 @@ DataAxis.prototype.addGroup = function(label, graphOptions) {
}; };
DataAxis.prototype.updateGroup = function(label, graphOptions) { DataAxis.prototype.updateGroup = function(label, graphOptions) {
if (!this.groups.hasOwnProperty(label)) {
this.amountOfGroups += 1;
}
this.groups[label] = graphOptions; this.groups[label] = graphOptions;
}; };

+ 97
- 92
lib/timeline/component/LineGraph.js View File

@ -66,6 +66,7 @@ function LineGraph(body, options) {
this.abortedGraphUpdate = false; this.abortedGraphUpdate = false;
this.updateSVGheight = false; this.updateSVGheight = false;
this.updateSVGheightOnResize = false; this.updateSVGheightOnResize = false;
this.forceGraphUpdate = true;
var me = this; var me = this;
this.itemsData = null; // DataSet this.itemsData = null; // DataSet
@ -105,18 +106,18 @@ function LineGraph(body, options) {
this.svgElements = {}; this.svgElements = {};
this.setOptions(options); this.setOptions(options);
this.groupsUsingDefaultStyles = [0]; this.groupsUsingDefaultStyles = [0];
this.COUNTER = 0;
this.body.emitter.on('rangechanged', function () { this.body.emitter.on('rangechanged', function () {
me.lastStart = me.body.range.start; me.lastStart = me.body.range.start;
me.svg.style.left = util.option.asSize(-me.props.width); me.svg.style.left = util.option.asSize(-me.props.width);
me.redraw.call(me, true);
me.forceGraphUpdate = true;
//Is this local redraw necessary? (Core also does a change event!)
me.redraw.call(me);
}); });
// create the HTML DOM // create the HTML DOM
this._create(); this._create();
this.framework = {svg: this.svg, svgElements: this.svgElements, options: this.options, groups: this.groups}; this.framework = {svg: this.svg, svgElements: this.svgElements, options: this.options, groups: this.groups};
this.body.emitter.emit('change');
} }
LineGraph.prototype = new Component(); LineGraph.prototype = new Component();
@ -158,7 +159,7 @@ LineGraph.prototype._create = function () {
LineGraph.prototype.setOptions = function (options) { LineGraph.prototype.setOptions = function (options) {
if (options) { if (options) {
var fields = ['sampling', 'defaultGroup', 'stack', 'height', 'graphHeight', 'yAxisOrientation', 'style', 'barChart', 'dataAxis', 'sort', 'groups']; var fields = ['sampling', 'defaultGroup', 'stack', 'height', 'graphHeight', 'yAxisOrientation', 'style', 'barChart', 'dataAxis', 'sort', 'groups'];
if (options.graphHeight === undefined && options.height !== undefined && this.body.domProps.centerContainer.height !== undefined) {
if (options.graphHeight === undefined && options.height !== undefined) {
this.updateSVGheight = true; this.updateSVGheight = true;
this.updateSVGheightOnResize = true; this.updateSVGheightOnResize = true;
} }
@ -210,8 +211,9 @@ LineGraph.prototype.setOptions = function (options) {
} }
// this is used to redraw the graph if the visibility of the groups is changed. // this is used to redraw the graph if the visibility of the groups is changed.
if (this.dom.frame) {
this.redraw(true);
if (this.dom.frame) { //not on initial run?
this.forceGraphUpdate=true;
this.body.emitter.emit("change");
} }
}; };
@ -331,7 +333,6 @@ LineGraph.prototype.setGroups = function (groups) {
LineGraph.prototype._onUpdate = function (ids) { LineGraph.prototype._onUpdate = function (ids) {
this._updateAllGroupData(); this._updateAllGroupData();
this.redraw(true);
}; };
LineGraph.prototype._onAdd = function (ids) { LineGraph.prototype._onAdd = function (ids) {
this._onUpdate(ids); this._onUpdate(ids);
@ -341,7 +342,6 @@ LineGraph.prototype._onRemove = function (ids) {
}; };
LineGraph.prototype._onUpdateGroups = function (groupIds) { LineGraph.prototype._onUpdateGroups = function (groupIds) {
this._updateAllGroupData(); this._updateAllGroupData();
this.redraw(true);
}; };
LineGraph.prototype._onAddGroups = function (groupIds) { LineGraph.prototype._onAddGroups = function (groupIds) {
this._onUpdateGroups(groupIds); this._onUpdateGroups(groupIds);
@ -356,7 +356,8 @@ LineGraph.prototype._onRemoveGroups = function (groupIds) {
for (var i = 0; i < groupIds.length; i++) { for (var i = 0; i < groupIds.length; i++) {
this._removeGroup(groupIds[i]); this._removeGroup(groupIds[i]);
} }
this.redraw(true);
this.forceGraphUpdate = true;
this.body.emitter.emit("change");
}; };
/** /**
@ -404,10 +405,16 @@ LineGraph.prototype._updateGroup = function (group, groupId) {
if (this.groups[groupId].options.yAxisOrientation == 'right') { if (this.groups[groupId].options.yAxisOrientation == 'right') {
this.yAxisRight.updateGroup(groupId, this.groups[groupId]); this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
this.legendRight.updateGroup(groupId, this.groups[groupId]); this.legendRight.updateGroup(groupId, this.groups[groupId]);
//If yAxisOrientation changed, clean out the group from the other axis.
this.yAxisLeft.removeGroup(groupId);
this.legendLeft.removeGroup(groupId);
} }
else { else {
this.yAxisLeft.updateGroup(groupId, this.groups[groupId]); this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
this.legendLeft.updateGroup(groupId, this.groups[groupId]); this.legendLeft.updateGroup(groupId, this.groups[groupId]);
//If yAxisOrientation changed, clean out the group from the other axis.
this.yAxisRight.removeGroup(groupId);
this.legendRight.removeGroup(groupId);
} }
} }
this.legendLeft.redraw(); this.legendLeft.redraw();
@ -484,6 +491,8 @@ LineGraph.prototype._updateAllGroupData = function () {
} }
} }
} }
this.forceGraphUpdate = true;
this.body.emitter.emit("change");
} }
}; };
@ -491,7 +500,7 @@ LineGraph.prototype._updateAllGroupData = function () {
* Redraw the component, mandatory function * Redraw the component, mandatory function
* @return {boolean} Returns true if the component is resized * @return {boolean} Returns true if the component is resized
*/ */
LineGraph.prototype.redraw = function (forceGraphUpdate) {
LineGraph.prototype.redraw = function () {
var resized = false; var resized = false;
// calculate actual size and position // calculate actual size and position
@ -500,9 +509,9 @@ LineGraph.prototype.redraw = function (forceGraphUpdate) {
- this.body.domProps.border.top - this.body.domProps.border.top
- this.body.domProps.border.bottom; - this.body.domProps.border.bottom;
// update the graph if there is no lastWidth or with, used for the initial draw
// update the graph if there is no lastWidth or width, used for the initial draw
if (this.lastWidth === undefined && this.props.width) { if (this.lastWidth === undefined && this.props.width) {
forceGraphUpdate = true;
this.forceGraphUpdate = true;
} }
// check if this component is resized // check if this component is resized
@ -539,8 +548,9 @@ LineGraph.prototype.redraw = function (forceGraphUpdate) {
} }
// zoomed is here to ensure that animations are shown correctly. // zoomed is here to ensure that animations are shown correctly.
if (resized == true || zoomed == true || this.abortedGraphUpdate == true || forceGraphUpdate == true) {
if (resized == true || zoomed == true || this.abortedGraphUpdate == true || this.forceGraphUpdate == true) {
resized = this._updateGraph() || resized; resized = this._updateGraph() || resized;
this.forceGraphUpdate = false;
} }
else { else {
// move the whole svg while dragging // move the whole svg while dragging
@ -554,7 +564,6 @@ LineGraph.prototype.redraw = function (forceGraphUpdate) {
} }
} }
} }
this.legendLeft.redraw(); this.legendLeft.redraw();
this.legendRight.redraw(); this.legendRight.redraw();
return resized; return resized;
@ -621,101 +630,97 @@ LineGraph.prototype._updateGraph = function () {
this._getYRanges(groupIds, groupsData, groupRanges); this._getYRanges(groupIds, groupsData, groupRanges);
// update the Y axis first, we use this data to draw at the correct Y points // 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); changeCalled = this._updateYAxis(groupIds, groupRanges);
var MAX_CYCLES = 5;
if (changeCalled == true && this.COUNTER < MAX_CYCLES) {
// at changeCalled, abort this update cycle as the graph needs another update with new Width input from the Redraw container.
// Cleanup SVG elements on abort.
if (changeCalled == true) {
DOMutil.cleanupElements(this.svgElements); DOMutil.cleanupElements(this.svgElements);
this.abortedGraphUpdate = true; this.abortedGraphUpdate = true;
this.COUNTER++;
this.body.emitter.emit('change');
return true; return true;
} }
else {
if (this.COUNTER > MAX_CYCLES) {
console.log("WARNING: there may be an infinite loop in the _updateGraph emitter cycle.");
}
this.COUNTER = 0;
this.abortedGraphUpdate = false;
// With the yAxis scaled correctly, use this to get the Y values of the points.
var below = undefined;
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (this.options.stack === true && this.options.style === 'line') {
if (group.options.excludeFromStacking == undefined || !group.options.excludeFromStacking) {
if (below != undefined) {
this._stack(groupsData[group.id], groupsData[below.id]);
if (group.options.shaded.enabled == true && group.options.shaded.orientation !== "group"){
if (group.options.shaded.orientation == "top" && below.options.shaded.orientation !== "group"){
below.options.shaded.orientation="group";
below.options.shaded.groupId=group.id;
} else {
group.options.shaded.orientation="group";
group.options.shaded.groupId=below.id;
}
this.abortedGraphUpdate = false;
// With the yAxis scaled correctly, use this to get the Y values of the points.
var below = undefined;
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (this.options.stack === true && this.options.style === 'line') {
if (group.options.excludeFromStacking == undefined || !group.options.excludeFromStacking) {
if (below != undefined) {
this._stack(groupsData[group.id], groupsData[below.id]);
if (group.options.shaded.enabled == true && group.options.shaded.orientation !== "group"){
if (group.options.shaded.orientation == "top" && below.options.shaded.orientation !== "group"){
below.options.shaded.orientation="group";
below.options.shaded.groupId=group.id;
} else {
group.options.shaded.orientation="group";
group.options.shaded.groupId=below.id;
} }
} }
below = group;
} }
below = group;
} }
this._convertYcoordinates(groupsData[groupIds[i]], group);
} }
this._convertYcoordinates(groupsData[groupIds[i]], group);
}
//Precalculate paths and draw shading if appropriate. This will make sure the shading is always behind any lines.
var paths = {};
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (group.options.style === 'line' && group.options.shaded.enabled == true) {
var dataset = groupsData[groupIds[i]];
if (!paths.hasOwnProperty(groupIds[i])) {
paths[groupIds[i]] = Lines.calcPath(dataset, group);
}
if (group.options.shaded.orientation === "group") {
var subGroupId = group.options.shaded.groupId;
if (groupIds.indexOf(subGroupId) === -1) {
console.log(group.id + ": Unknown shading group target given:" + subGroupId);
continue;
}
if (!paths.hasOwnProperty(subGroupId)) {
paths[subGroupId] = Lines.calcPath(groupsData[subGroupId], this.groups[subGroupId]);
}
Lines.drawShading(paths[groupIds[i]], group, paths[subGroupId], this.framework);
//Precalculate paths and draw shading if appropriate. This will make sure the shading is always behind any lines.
var paths = {};
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (group.options.style === 'line' && group.options.shaded.enabled == true) {
var dataset = groupsData[groupIds[i]];
if (dataset == null || dataset.length == 0) {
continue;
}
if (!paths.hasOwnProperty(groupIds[i])) {
paths[groupIds[i]] = Lines.calcPath(dataset, group);
}
if (group.options.shaded.orientation === "group") {
var subGroupId = group.options.shaded.groupId;
if (groupIds.indexOf(subGroupId) === -1) {
console.log(group.id + ": Unknown shading group target given:" + subGroupId);
continue;
} }
else {
Lines.drawShading(paths[groupIds[i]], group, undefined, this.framework);
if (!paths.hasOwnProperty(subGroupId)) {
paths[subGroupId] = Lines.calcPath(groupsData[subGroupId], this.groups[subGroupId]);
} }
Lines.drawShading(paths[groupIds[i]], group, paths[subGroupId], this.framework);
}
else {
Lines.drawShading(paths[groupIds[i]], group, undefined, this.framework);
} }
} }
}
// draw the groups, calculating paths if still necessary.
Bars.draw(groupIds, groupsData, this.framework);
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (groupsData[groupIds[i]].length > 0) {
switch (group.options.style) {
case "line":
if (!paths.hasOwnProperty(groupIds[i])) {
paths[groupIds[i]] = Lines.calcPath(groupsData[groupIds[i]], group);
}
Lines.draw(paths[groupIds[i]], group, this.framework);
//explicit no break;
case "point":
//explicit no break;
case "points":
if (group.options.style == "point" || group.options.style == "points" || group.options.drawPoints.enabled == true) {
Points.draw(groupsData[groupIds[i]], group, this.framework);
}
break;
case "bar":
// bar needs to be drawn enmasse
//explicit no break
default:
//do nothing...
}
// draw the groups, calculating paths if still necessary.
Bars.draw(groupIds, groupsData, this.framework);
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (groupsData[groupIds[i]].length > 0) {
switch (group.options.style) {
case "line":
if (!paths.hasOwnProperty(groupIds[i])) {
paths[groupIds[i]] = Lines.calcPath(groupsData[groupIds[i]], group);
}
Lines.draw(paths[groupIds[i]], group, this.framework);
//explicit no break;
case "point":
//explicit no break;
case "points":
if (group.options.style == "point" || group.options.style == "points" || group.options.drawPoints.enabled == true) {
Points.draw(groupsData[groupIds[i]], group, this.framework);
}
break;
case "bar":
// bar needs to be drawn enmasse
//explicit no break
default:
//do nothing...
} }
} }
} }
} }
} }

Loading…
Cancel
Save