Browse Source

[Timeline] Year quarters (#3717)

* timeline: add 'quarter' support

* add unit test

* update docs

* misc

* 'quarters' scale hidden by default
develop
Francesco Stefanini 6 years ago
committed by Yotam Berkowitz
parent
commit
716f361648
7 changed files with 76 additions and 9 deletions
  1. +3
    -1
      docs/graph2d/index.html
  2. +5
    -3
      docs/timeline/index.html
  3. +6
    -0
      examples/timeline/other/functionLabelFormats.html
  4. +44
    -4
      lib/timeline/TimeStep.js
  5. +4
    -0
      lib/timeline/optionsGraph2d.js
  6. +5
    -1
      lib/timeline/optionsTimeline.js
  7. +9
    -0
      test/TimeStep.test.js

+ 3
- 1
docs/graph2d/index.html View File

@ -841,6 +841,7 @@ function (option, path) {
weekday: 'ddd D', weekday: 'ddd D',
day: 'D', day: 'D',
month: 'MMM', month: 'MMM',
quarter: '[Q]Q',
year: 'YYYY' year: 'YYYY'
}, },
majorLabels: { majorLabels: {
@ -851,6 +852,7 @@ function (option, path) {
weekday: 'MMMM YYYY', weekday: 'MMMM YYYY',
day: 'MMMM YYYY', day: 'MMMM YYYY',
month: 'YYYY', month: 'YYYY',
quarter: 'YYYY',
year: '' year: ''
} }
}</pre> }</pre>
@ -1009,7 +1011,7 @@ function (option, path) {
<td class="indent">timeAxis.scale</td> <td class="indent">timeAxis.scale</td>
<td>String</td> <td>String</td>
<td>none</td> <td>none</td>
<td>Set a fixed scale for the time axis of the Timeline. Choose from <code>'millisecond'</code>, <code>'second'</code>, <code>'minute'</code>, <code>'hour'</code>, <code>'weekday'</code>, <code>'day'</code>, <code>'month'</code>, <code>'year'</code>. Example usage:
<td>Set a fixed scale for the time axis of the Timeline. Choose from <code>'millisecond'</code>, <code>'second'</code>, <code>'minute'</code>, <code>'hour'</code>, <code>'weekday'</code>, <code>'day'</code>, <code>'month'</code>, <code>'quarter'</code>, <code>'year'</code>. Example usage:
<pre class="prettyprint lang-js options">var options = { <pre class="prettyprint lang-js options">var options = {
timeAxis: {scale: 'minute', step: 5} timeAxis: {scale: 'minute', step: 5}
}</pre> }</pre>

+ 5
- 3
docs/timeline/index.html View File

@ -623,6 +623,7 @@ function (option, path) {
day: 'D', day: 'D',
week: 'w', week: 'w',
month: 'MMM', month: 'MMM',
quarter: '[Q]Q',
year: 'YYYY' year: 'YYYY'
}, },
majorLabels: { majorLabels: {
@ -634,6 +635,7 @@ function (option, path) {
day: 'MMMM YYYY', day: 'MMMM YYYY',
week: 'MMMM YYYY', week: 'MMMM YYYY',
month: 'YYYY', month: 'YYYY',
quarter: 'YYYY',
year: '' year: ''
} }
}</pre> }</pre>
@ -1127,7 +1129,7 @@ function (option, path) {
<td>function</td> <td>function</td>
<td>When moving items on the Timeline, they will be snapped to nice dates like full hours or days, depending on the current scale. The <code>snap</code> function can be replaced with a custom function, or can be set to <code>null</code> to disable snapping. The signature of the snap function is: <td>When moving items on the Timeline, they will be snapped to nice dates like full hours or days, depending on the current scale. The <code>snap</code> function can be replaced with a custom function, or can be set to <code>null</code> to disable snapping. The signature of the snap function is:
<pre class="prettyprint lang-js">function snap(date: Date, scale: string, step: number) : Date or number</pre> <pre class="prettyprint lang-js">function snap(date: Date, scale: string, step: number) : Date or number</pre>
The parameter <code>scale</code> can be can be 'millisecond', 'second', 'minute', 'hour', 'weekday, 'week', 'day, 'month, or 'year'. The parameter <code>step</code> is a number like 1, 2, 4, 5.
The parameter <code>scale</code> can be can be 'millisecond', 'second', 'minute', 'hour', 'weekday, 'week', 'day', 'month', 'quarter' or 'year'. The parameter <code>step</code> is a number like 1, 2, 4, 5.
</td> </td>
</tr> </tr>
@ -1171,7 +1173,7 @@ function (option, path) {
<td class="indent">timeAxis.scale</td> <td class="indent">timeAxis.scale</td>
<td>String</td> <td>String</td>
<td>none</td> <td>none</td>
<td>Set a fixed scale for the time axis of the Timeline. Choose from <code>'millisecond'</code>, <code>'second'</code>, <code>'minute'</code>, <code>'hour'</code>, <code>'weekday'</code>, <code>'week'</code>, <code>'day'</code>, <code>'month'</code>, <code>'year'</code>. Example usage:
<td>Set a fixed scale for the time axis of the Timeline. Choose from <code>'millisecond'</code>, <code>'second'</code>, <code>'minute'</code>, <code>'hour'</code>, <code>'weekday'</code>, <code>'week'</code>, <code>'day'</code>, <code>'month'</code>, <code>'quarter'</code>, <code>'year'</code>. Example usage:
<pre class="prettyprint lang-js">var options = { <pre class="prettyprint lang-js">var options = {
timeAxis: {scale: 'minute', step: 5} timeAxis: {scale: 'minute', step: 5}
}</pre> }</pre>
@ -2153,7 +2155,7 @@ var options = {
</tr> </tr>
</table> </table>
<p> <p>
Note: the 'week' scale is not included in the automatic zoom levels as its scale is not a direct logical successor of 'days' nor a logical predecessor of 'months'
Note: the 'week' scale is not included in the automatic zoom levels as its scale is not a direct logical successor of 'days' nor a logical predecessor of 'months'. Same goes for the 'quarter' scale which is not a direct logical successor of 'months' nor a logical predecessor of 'years'.
</p> </p>
<p>Examples:</p> <p>Examples:</p>

+ 6
- 0
examples/timeline/other/functionLabelFormats.html View File

@ -86,6 +86,9 @@
case 'month': case 'month':
divider = 1000 * 60 * 60 * 24 * 30; divider = 1000 * 60 * 60 * 24 * 30;
break; break;
case 'quarter':
divider = 1000 * 60 * 60 * 24 * 30 * 3;
break;
case 'year': case 'year':
divider = 1000 * 60 * 60 * 24 * 365; divider = 1000 * 60 * 60 * 24 * 365;
break; break;
@ -120,6 +123,9 @@
case 'month': case 'month':
divider = 1000 * 60 * 60 * 24 * 30; divider = 1000 * 60 * 60 * 24 * 30;
break; break;
case 'quarter':
divider = 1000 * 60 * 60 * 24 * 30 * 3;
break;
case 'year': case 'year':
divider = 1000 * 60 * 60 * 24 * 365; divider = 1000 * 60 * 60 * 24 * 365;
break; break;

+ 44
- 4
lib/timeline/TimeStep.js View File

@ -75,6 +75,7 @@ TimeStep.FORMAT = {
day: 'D', day: 'D',
week: 'D', week: 'D',
month: 'MMM', month: 'MMM',
quarter: 'MMM',
year: 'YYYY' year: 'YYYY'
}, },
majorLabels: { majorLabels: {
@ -86,6 +87,7 @@ TimeStep.FORMAT = {
day: 'MMMM YYYY', day: 'MMMM YYYY',
week: 'MMMM YYYY', week: 'MMMM YYYY',
month: 'YYYY', month: 'YYYY',
quarter: 'YYYY',
year: '' year: ''
} }
}; };
@ -107,7 +109,7 @@ TimeStep.prototype.setMoment = function (moment) {
/** /**
* Set custom formatting for the minor an major labels of the TimeStep. * Set custom formatting for the minor an major labels of the TimeStep.
* Both `minorLabels` and `majorLabels` are an Object with properties: * Both `minorLabels` and `majorLabels` are an Object with properties:
* 'millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'week', 'month', 'year'.
* 'millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'week', 'month', 'quarter', 'year'.
* @param {{minorLabels: Object, majorLabels: Object}} format * @param {{minorLabels: Object, majorLabels: Object}} format
*/ */
TimeStep.prototype.setFormat = function (format) { TimeStep.prototype.setFormat = function (format) {
@ -162,6 +164,7 @@ TimeStep.prototype.roundToMinor = function() {
case 'year': case 'year':
this.current.year(this.step * Math.floor(this.current.year() / this.step)); this.current.year(this.step * Math.floor(this.current.year() / this.step));
this.current.month(0); this.current.month(0);
case 'quarter': this.current.month(0); // eslint-disable-line no-fallthrough
case 'month': this.current.date(1); // eslint-disable-line no-fallthrough case 'month': this.current.date(1); // eslint-disable-line no-fallthrough
case 'week': // eslint-disable-line no-fallthrough case 'week': // eslint-disable-line no-fallthrough
case 'day': // eslint-disable-line no-fallthrough case 'day': // eslint-disable-line no-fallthrough
@ -183,6 +186,7 @@ TimeStep.prototype.roundToMinor = function() {
case 'day': this.current.subtract((this.current.date() - 1) % this.step, 'day'); break; case 'day': this.current.subtract((this.current.date() - 1) % this.step, 'day'); break;
case 'week': this.current.subtract(this.current.week() % this.step, 'week'); break; case 'week': this.current.subtract(this.current.week() % this.step, 'week'); break;
case 'month': this.current.subtract(this.current.month() % this.step, 'month'); break; case 'month': this.current.subtract(this.current.month() % this.step, 'month'); break;
case 'quarter': this.current.subtract((this.current.quarter() - 1) % this.step, 'quarter'); break;
case 'year': this.current.subtract(this.current.year() % this.step, 'year'); break; case 'year': this.current.subtract(this.current.year() % this.step, 'year'); break;
default: break; default: break;
} }
@ -240,6 +244,7 @@ TimeStep.prototype.next = function() {
} }
break; break;
case 'month': this.current.add(this.step, 'month'); break; case 'month': this.current.add(this.step, 'month'); break;
case 'quarter': this.current.add(this.step, 'quarter'); break;
case 'year': this.current.add(this.step, 'year'); break; case 'year': this.current.add(this.step, 'year'); break;
default: break; default: break;
} }
@ -255,6 +260,7 @@ TimeStep.prototype.next = function() {
case 'day': if(this.current.date() < this.step+1) this.current.date(1); break; case 'day': if(this.current.date() < this.step+1) this.current.date(1); break;
case 'week': if(this.current.week() < this.step) this.current.week(1); break; // week numbering starts at 1, not 0 case 'week': if(this.current.week() < this.step) this.current.week(1); break; // week numbering starts at 1, not 0
case 'month': if(this.current.month() < this.step) this.current.month(0); break; case 'month': if(this.current.month() < this.step) this.current.month(0); break;
case 'quarter': if(this.current.quarter() < this.step+1) this.current.quarter(1); break;
case 'year': break; // nothing to do for year case 'year': break; // nothing to do for year
default: break; default: break;
} }
@ -290,7 +296,7 @@ TimeStep.prototype.getCurrent = function() {
* @param {{scale: string, step: number}} params * @param {{scale: string, step: number}} params
* An object containing two properties: * An object containing two properties:
* - A string 'scale'. Choose from 'millisecond', 'second', * - A string 'scale'. Choose from 'millisecond', 'second',
* 'minute', 'hour', 'weekday', 'day', 'week', 'month', 'year'.
* 'minute', 'hour', 'weekday', 'day', 'week', 'month', 'quarter, 'year'.
* - A number 'step'. A step size, by default 1. * - A number 'step'. A step size, by default 1.
* Choose for example 1, 2, 5, or 10. * Choose for example 1, 2, 5, or 10.
*/ */
@ -323,6 +329,7 @@ TimeStep.prototype.setMinimumStep = function(minimumStep) {
//var b = asc + ds; //var b = asc + ds;
var stepYear = (1000 * 60 * 60 * 24 * 30 * 12); var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
var stepQuarter = (1000 * 60 * 60 * 24 * 30 * 3);
var stepMonth = (1000 * 60 * 60 * 24 * 30); var stepMonth = (1000 * 60 * 60 * 24 * 30);
var stepDay = (1000 * 60 * 60 * 24); var stepDay = (1000 * 60 * 60 * 24);
var stepHour = (1000 * 60 * 60); var stepHour = (1000 * 60 * 60);
@ -338,7 +345,7 @@ TimeStep.prototype.setMinimumStep = function(minimumStep) {
if (stepYear*10 > minimumStep) {this.scale = 'year'; this.step = 10;} if (stepYear*10 > minimumStep) {this.scale = 'year'; this.step = 10;}
if (stepYear*5 > minimumStep) {this.scale = 'year'; this.step = 5;} if (stepYear*5 > minimumStep) {this.scale = 'year'; this.step = 5;}
if (stepYear > minimumStep) {this.scale = 'year'; this.step = 1;} if (stepYear > minimumStep) {this.scale = 'year'; this.step = 1;}
if (stepMonth*3 > minimumStep) {this.scale = 'month'; this.step = 3;}
if (stepQuarter > minimumStep) {this.scale = 'quarter'; this.step = 1;}
if (stepMonth > minimumStep) {this.scale = 'month'; this.step = 1;} if (stepMonth > minimumStep) {this.scale = 'month'; this.step = 1;}
if (stepDay*7 > minimumStep) {this.scale = 'week'; this.step = 1;} if (stepDay*7 > minimumStep) {this.scale = 'week'; this.step = 1;}
if (stepDay*2 > minimumStep) {this.scale = 'day'; this.step = 2;} if (stepDay*2 > minimumStep) {this.scale = 'day'; this.step = 2;}
@ -368,7 +375,7 @@ TimeStep.prototype.setMinimumStep = function(minimumStep) {
* Static function * Static function
* @param {Date} date the date to be snapped. * @param {Date} date the date to be snapped.
* @param {string} scale Current scale, can be 'millisecond', 'second', * @param {string} scale Current scale, can be 'millisecond', 'second',
* 'minute', 'hour', 'weekday, 'day', 'week', 'month', 'year'.
* 'minute', 'hour', 'weekday, 'day', 'week', 'month', 'quarter', 'year'.
* @param {number} step Current step (1, 2, 4, 5, ... * @param {number} step Current step (1, 2, 4, 5, ...
* @return {Date} snappedDate * @return {Date} snappedDate
*/ */
@ -385,6 +392,22 @@ TimeStep.snap = function(date, scale, step) {
clone.seconds(0); clone.seconds(0);
clone.milliseconds(0); clone.milliseconds(0);
} }
else if (scale == 'quarter') {
if ((clone.month() % 3 == 1 && clone.date() > 15) || clone.month() % 3 == 2) {
clone.date(1);
clone.month(Math.floor(clone.month() / 3) * 3);
clone.add(1, 'quarter');
// important: first set Date to 1, after that change the month and the quarter.
} else {
clone.date(1);
clone.month(Math.floor(clone.month() / 3) * 3);
}
clone.hours(0);
clone.minutes(0);
clone.seconds(0);
clone.milliseconds(0);
}
else if (scale == 'month') { else if (scale == 'month') {
if (clone.date() > 15) { if (clone.date() > 15) {
clone.date(1); clone.date(1);
@ -495,6 +518,7 @@ TimeStep.prototype.isMajor = function() {
if (this.switchedYear == true) { if (this.switchedYear == true) {
switch (this.scale) { switch (this.scale) {
case 'year': case 'year':
case 'quarter':
case 'month': case 'month':
case 'week': case 'week':
case 'weekday': case 'weekday':
@ -551,6 +575,8 @@ TimeStep.prototype.isMajor = function() {
return (date.date() == 1); return (date.date() == 1);
case 'month': case 'month':
return (date.month() == 0); return (date.month() == 0);
case 'quarter':
return (date.quarter() == 1);
case 'year': case 'year':
return false; return false;
default: default:
@ -665,6 +691,15 @@ TimeStep.prototype.getClassName = function() {
return date.isSame(new Date(), 'month') ? ' vis-current-month' : ''; return date.isSame(new Date(), 'month') ? ' vis-current-month' : '';
} }
/**
*
* @param {Date} date
* @returns {String}
*/
function currentQuarter(date) {
return date.isSame(new Date(), 'quarter') ? ' vis-current-quarter' : '';
}
/** /**
* *
* @param {Date} date * @param {Date} date
@ -717,6 +752,11 @@ TimeStep.prototype.getClassName = function() {
classNames.push(currentMonth(current)); classNames.push(currentMonth(current));
classNames.push(even(current.month())); classNames.push(even(current.month()));
break; break;
case 'quarter':
classNames.push('vis-q' + current.quarter());
classNames.push(currentQuarter(current));
classNames.push(even(current.quarter()));
break;
case 'year': case 'year':
classNames.push('vis-year' + current.year()); classNames.push('vis-year' + current.year());
classNames.push(currentYear(current)); classNames.push(currentYear(current));

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

@ -113,6 +113,7 @@ let allOptions = {
weekday: {string,'undefined': 'undefined'}, weekday: {string,'undefined': 'undefined'},
day: {string,'undefined': 'undefined'}, day: {string,'undefined': 'undefined'},
month: {string,'undefined': 'undefined'}, month: {string,'undefined': 'undefined'},
quarter: {string,'undefined': 'undefined'},
year: {string,'undefined': 'undefined'}, year: {string,'undefined': 'undefined'},
__type__: {object} __type__: {object}
}, },
@ -124,6 +125,7 @@ let allOptions = {
weekday: {string,'undefined': 'undefined'}, weekday: {string,'undefined': 'undefined'},
day: {string,'undefined': 'undefined'}, day: {string,'undefined': 'undefined'},
month: {string,'undefined': 'undefined'}, month: {string,'undefined': 'undefined'},
quarter: {string,'undefined': 'undefined'},
year: {string,'undefined': 'undefined'}, year: {string,'undefined': 'undefined'},
__type__: {object} __type__: {object}
}, },
@ -238,6 +240,7 @@ let configureOptions = {
weekday: 'ddd D', weekday: 'ddd D',
day: 'D', day: 'D',
month: 'MMM', month: 'MMM',
quarter: '[Q]Q',
year: 'YYYY' year: 'YYYY'
}, },
majorLabels: { majorLabels: {
@ -248,6 +251,7 @@ let configureOptions = {
weekday: 'MMMM YYYY', weekday: 'MMMM YYYY',
day: 'MMMM YYYY', day: 'MMMM YYYY',
month: 'YYYY', month: 'YYYY',
quarter: 'YYYY',
year: '' year: ''
} }
}, },

+ 5
- 1
lib/timeline/optionsTimeline.js View File

@ -62,6 +62,7 @@ let allOptions = {
day: {string,'undefined': 'undefined'}, day: {string,'undefined': 'undefined'},
week: {string,'undefined': 'undefined'}, week: {string,'undefined': 'undefined'},
month: {string,'undefined': 'undefined'}, month: {string,'undefined': 'undefined'},
quarter: {string,'undefined': 'undefined'},
year: {string,'undefined': 'undefined'}, year: {string,'undefined': 'undefined'},
__type__: {object, 'function': 'function'} __type__: {object, 'function': 'function'}
}, },
@ -74,6 +75,7 @@ let allOptions = {
day: {string,'undefined': 'undefined'}, day: {string,'undefined': 'undefined'},
week: {string,'undefined': 'undefined'}, week: {string,'undefined': 'undefined'},
month: {string,'undefined': 'undefined'}, month: {string,'undefined': 'undefined'},
quarter: {string,'undefined': 'undefined'},
year: {string,'undefined': 'undefined'}, year: {string,'undefined': 'undefined'},
__type__: {object, 'function': 'function'} __type__: {object, 'function': 'function'}
}, },
@ -202,6 +204,7 @@ let configureOptions = {
day: 'D', day: 'D',
week: 'w', week: 'w',
month: 'MMM', month: 'MMM',
quarter: '[Q]Q',
year: 'YYYY' year: 'YYYY'
}, },
majorLabels: { majorLabels: {
@ -213,6 +216,7 @@ let configureOptions = {
day: 'MMMM YYYY', day: 'MMMM YYYY',
week: 'MMMM YYYY', week: 'MMMM YYYY',
month: 'YYYY', month: 'YYYY',
quarter: 'YYYY',
year: '' year: ''
} }
}, },
@ -257,7 +261,7 @@ let configureOptions = {
start: '', start: '',
//template: {'function': 'function'}, //template: {'function': 'function'},
//timeAxis: { //timeAxis: {
// scale: ['millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'week', 'month', 'year'],
// scale: ['millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'week', 'month', 'quarter', 'year'],
// step: [1, 1, 10, 1] // step: [1, 1, 10, 1]
//}, //},
showTooltips: true, showTooltips: true,

+ 9
- 0
test/TimeStep.test.js View File

@ -55,6 +55,15 @@ describe('TimeStep', function () {
assert.equal(timestep.getCurrent().unix(), moment("2018-01-01T00:00:00.000").unix(), "should have the right value after a step"); assert.equal(timestep.getCurrent().unix(), moment("2018-01-01T00:00:00.000").unix(), "should have the right value after a step");
}); });
it('should perform the step with a specified scale (1 quarter)', function () {
var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5));
timestep.setScale({ scale: 'quarter', step: 1 });
timestep.start();
assert.equal(timestep.getCurrent().unix(), moment("2017-01-01T00:00:00.000").unix(), "should have the right initial value");
timestep.next();
assert.equal(timestep.getCurrent().unix(), moment("2017-04-01T00:00:00.000").unix(), "should have the right value after a step");
});
it('should perform the step with a specified scale (1 month)', function () { it('should perform the step with a specified scale (1 month)', function () {
var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5)); var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5));
timestep.setScale({ scale: 'month', step: 1 }); timestep.setScale({ scale: 'month', step: 1 });

Loading…
Cancel
Save