@@ -64,6 +64,22 @@ function sorter(a, b) {
6464 return a - b ;
6565}
6666
67+ function arrayUnique ( items ) {
68+ var hash = { } ;
69+ var out = [ ] ;
70+ var i , ilen , item ;
71+
72+ for ( i = 0 , ilen = items . length ; i < ilen ; ++ i ) {
73+ item = items [ i ] ;
74+ if ( ! hash [ item ] ) {
75+ hash [ item ] = true ;
76+ out . push ( item ) ;
77+ }
78+ }
79+
80+ return out ;
81+ }
82+
6783/**
6884 * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
6985 * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
@@ -73,31 +89,33 @@ function sorter(a, b) {
7389 * to create the lookup table. The table ALWAYS contains at least two items: min and max.
7490 *
7591 * @param {Number[] } timestamps - timestamps sorted from lowest to highest.
76- * @param {Boolean } linear - If true , timestamps will be spread linearly along the min/max
77- * range, so basically, the table will contains only two items: {min, 0} and {max, 1}. If
78- * false , timestamps will be positioned at the same distance from each other. In this case,
79- * only timestamps that break the time linearity are registered, meaning that in the best
80- * case, all timestamps are linear, the table contains only min and max.
92+ * @param {String } distribution - If 'linear' , timestamps will be spread linearly along the min
93+ * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
94+ * If 'series' , timestamps will be positioned at the same distance from each other. In this
95+ * case, only timestamps that break the time linearity are registered, meaning that in the
96+ * best case, all timestamps are linear, the table contains only min and max.
8197 */
82- function buildLookupTable ( timestamps , min , max , linear ) {
83- if ( linear || ! timestamps . length ) {
98+ function buildLookupTable ( timestamps , min , max , distribution ) {
99+ if ( distribution === ' linear' || ! timestamps . length ) {
84100 return [
85101 { time : min , pos : 0 } ,
86102 { time : max , pos : 1 }
87103 ] ;
88104 }
89105
90106 var table = [ ] ;
91- var items = timestamps . slice ( 0 ) ;
107+ var items = [ min ] ;
92108 var i , ilen , prev , curr , next ;
93109
94- if ( min < timestamps [ 0 ] ) {
95- items . unshift ( min ) ;
96- }
97- if ( max > timestamps [ timestamps . length - 1 ] ) {
98- items . push ( max ) ;
110+ for ( i = 0 , ilen = timestamps . length ; i < ilen ; ++ i ) {
111+ curr = timestamps [ i ] ;
112+ if ( curr > min && curr < max ) {
113+ items . push ( curr ) ;
114+ }
99115 }
100116
117+ items . push ( max ) ;
118+
101119 for ( i = 0 , ilen = items . length ; i < ilen ; ++ i ) {
102120 next = items [ i + 1 ] ;
103121 prev = items [ i - 1 ] ;
@@ -334,6 +352,15 @@ module.exports = function(Chart) {
334352 var defaultConfig = {
335353 position : 'bottom' ,
336354
355+ /**
356+ * Data distribution along the scale:
357+ * - 'linear': data are spread according to their time (distances can vary),
358+ * - 'series': data are spread at the same distance from each other.
359+ * @see https://github.com/chartjs/Chart.js/pull/4507
360+ * @since 2.7.0
361+ */
362+ distribution : 'linear' ,
363+
337364 time : {
338365 parser : false , // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
339366 format : false , // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
@@ -359,15 +386,6 @@ module.exports = function(Chart) {
359386 ticks : {
360387 autoSkip : false ,
361388
362- /**
363- * Ticks distribution along the scale:
364- * - 'linear': ticks and data are spread according to their time (distances can vary),
365- * - 'series': ticks and data are spread at the same distance from each other.
366- * @see https://github.com/chartjs/Chart.js/pull/4507
367- * @since 2.7.0
368- */
369- mode : 'linear' ,
370-
371389 /**
372390 * Ticks generation input values:
373391 * - 'auto': generates "optimal" ticks based on scale size and time options.
@@ -430,44 +448,54 @@ module.exports = function(Chart) {
430448 var me = this ;
431449 var chart = me . chart ;
432450 var options = me . options ;
433- var datasets = chart . data . datasets || [ ] ;
434451 var min = parse ( options . time . min , me ) || MAX_INTEGER ;
435452 var max = parse ( options . time . max , me ) || MIN_INTEGER ;
436453 var timestamps = [ ] ;
454+ var datasets = [ ] ;
437455 var labels = [ ] ;
438456 var i , j , ilen , jlen , data , timestamp ;
439457
440458 // Convert labels to timestamps
441459 for ( i = 0 , ilen = chart . data . labels . length ; i < ilen ; ++ i ) {
442- timestamp = parse ( chart . data . labels [ i ] , me ) ;
443- min = Math . min ( min , timestamp ) ;
444- max = Math . max ( max , timestamp ) ;
445- labels . push ( timestamp ) ;
460+ labels . push ( parse ( chart . data . labels [ i ] , me ) ) ;
446461 }
447462
448463 // Convert data to timestamps
449- for ( i = 0 , ilen = datasets . length ; i < ilen ; ++ i ) {
464+ for ( i = 0 , ilen = ( chart . data . datasets || [ ] ) . length ; i < ilen ; ++ i ) {
450465 if ( chart . isDatasetVisible ( i ) ) {
451- data = datasets [ i ] . data ;
466+ data = chart . data . datasets [ i ] . data ;
452467
453468 // Let's consider that all data have the same format.
454469 if ( helpers . isObject ( data [ 0 ] ) ) {
455- timestamps [ i ] = [ ] ;
470+ datasets [ i ] = [ ] ;
456471
457472 for ( j = 0 , jlen = data . length ; j < jlen ; ++ j ) {
458473 timestamp = parse ( data [ j ] , me ) ;
459- min = Math . min ( min , timestamp ) ;
460- max = Math . max ( max , timestamp ) ;
461- timestamps [ i ] [ j ] = timestamp ;
474+ timestamps . push ( timestamp ) ;
475+ datasets [ i ] [ j ] = timestamp ;
462476 }
463477 } else {
464- timestamps [ i ] = labels . slice ( 0 ) ;
478+ timestamps . push . apply ( timestamps , labels ) ;
479+ datasets [ i ] = labels . slice ( 0 ) ;
465480 }
466481 } else {
467- timestamps [ i ] = [ ] ;
482+ datasets [ i ] = [ ] ;
468483 }
469484 }
470485
486+ if ( labels . length ) {
487+ // Sort labels **after** data have been converted
488+ labels = arrayUnique ( labels ) . sort ( sorter ) ;
489+ min = Math . min ( min , labels [ 0 ] ) ;
490+ max = Math . max ( max , labels [ labels . length - 1 ] ) ;
491+ }
492+
493+ if ( timestamps . length ) {
494+ timestamps = arrayUnique ( timestamps ) . sort ( sorter ) ;
495+ min = Math . min ( min , timestamps [ 0 ] ) ;
496+ max = Math . max ( max , timestamps [ timestamps . length - 1 ] ) ;
497+ }
498+
471499 // In case there is no valid min/max, let's use today limits
472500 min = min === MAX_INTEGER ? + moment ( ) . startOf ( 'day' ) : min ;
473501 max = max === MIN_INTEGER ? + moment ( ) . endOf ( 'day' ) + 1 : max ;
@@ -477,36 +505,36 @@ module.exports = function(Chart) {
477505 me . max = Math . max ( min + 1 , max ) ;
478506
479507 // PRIVATE
480- me . _datasets = timestamps ;
481508 me . _horizontal = me . isHorizontal ( ) ;
482- me . _labels = labels . sort ( sorter ) ; // Sort labels **after** data have been converted
483509 me . _table = [ ] ;
510+ me . _timestamps = {
511+ data : timestamps ,
512+ datasets : datasets ,
513+ labels : labels
514+ } ;
484515 } ,
485516
486517 buildTicks : function ( ) {
487518 var me = this ;
488519 var min = me . min ;
489520 var max = me . max ;
490- var timeOpts = me . options . time ;
491- var ticksOpts = me . options . ticks ;
521+ var options = me . options ;
522+ var timeOpts = options . time ;
523+ var ticksOpts = options . ticks ;
492524 var formats = timeOpts . displayFormats ;
493525 var capacity = me . getLabelCapacity ( min ) ;
494526 var unit = timeOpts . unit || determineUnit ( timeOpts . minUnit , min , max , capacity ) ;
495527 var majorUnit = determineMajorUnit ( unit ) ;
496528 var timestamps = [ ] ;
497529 var ticks = [ ] ;
498- var hash = { } ;
499530 var i , ilen , timestamp ;
500531
501532 switch ( ticksOpts . source ) {
502533 case 'data' :
503- for ( i = 0 , ilen = me . _datasets . length ; i < ilen ; ++ i ) {
504- timestamps . push . apply ( timestamps , me . _datasets [ i ] ) ;
505- }
506- timestamps . sort ( sorter ) ;
534+ timestamps = me . _timestamps . data ;
507535 break ;
508536 case 'labels' :
509- timestamps = me . _labels ;
537+ timestamps = me . _timestamps . labels ;
510538 break ;
511539 case 'auto' :
512540 default :
@@ -522,12 +550,10 @@ module.exports = function(Chart) {
522550 min = parse ( timeOpts . min , me ) || min ;
523551 max = parse ( timeOpts . max , me ) || max ;
524552
525- // Remove ticks outside the min/max range and duplicated entries
553+ // Remove ticks outside the min/max range
526554 for ( i = 0 , ilen = timestamps . length ; i < ilen ; ++ i ) {
527555 timestamp = timestamps [ i ] ;
528- if ( timestamp >= min && timestamp <= max && ! hash [ timestamp ] ) {
529- // hash is used to efficiently detect timestamp duplicates
530- hash [ timestamp ] = true ;
556+ if ( timestamp >= min && timestamp <= max ) {
531557 ticks . push ( timestamp ) ;
532558 }
533559 }
@@ -540,7 +566,7 @@ module.exports = function(Chart) {
540566 me . _majorUnit = majorUnit ;
541567 me . _minorFormat = formats [ unit ] ;
542568 me . _majorFormat = formats [ majorUnit ] ;
543- me . _table = buildLookupTable ( ticks , min , max , ticksOpts . mode === 'linear' ) ;
569+ me . _table = buildLookupTable ( me . _timestamps . data , min , max , options . distribution ) ;
544570
545571 return ticksFromTimestamps ( ticks , majorUnit ) ;
546572 } ,
@@ -609,7 +635,7 @@ module.exports = function(Chart) {
609635 var time = null ;
610636
611637 if ( index !== undefined && datasetIndex !== undefined ) {
612- time = me . _datasets [ datasetIndex ] [ index ] ;
638+ time = me . _timestamps . datasets [ datasetIndex ] [ index ] ;
613639 }
614640
615641 if ( time === null ) {
0 commit comments