@@ -23,13 +23,18 @@ var axAttrs = require('./layout_attributes');
2323var cleanTicks = require ( './clean_ticks' ) ;
2424
2525var constants = require ( '../../constants/numerical' ) ;
26+ var ONEMAXYEAR = constants . ONEMAXYEAR ;
2627var ONEAVGYEAR = constants . ONEAVGYEAR ;
28+ var ONEMINYEAR = constants . ONEMINYEAR ;
29+ var ONEMAXQUARTER = constants . ONEMAXQUARTER ;
2730var ONEAVGQUARTER = constants . ONEAVGQUARTER ;
31+ var ONEMINQUARTER = constants . ONEMINQUARTER ;
2832var ONEMAXMONTH = constants . ONEMAXMONTH ;
2933var ONEAVGMONTH = constants . ONEAVGMONTH ;
3034var ONEMINMONTH = constants . ONEMINMONTH ;
3135var ONEWEEK = constants . ONEWEEK ;
3236var ONEDAY = constants . ONEDAY ;
37+ var HALFDAY = ONEDAY / 2 ;
3338var ONEHOUR = constants . ONEHOUR ;
3439var ONEMIN = constants . ONEMIN ;
3540var ONESEC = constants . ONESEC ;
@@ -499,7 +504,7 @@ function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
499504 // will always give a somewhat odd-looking label, until we do something
500505 // smarter like showing the bin boundaries (or the bounds of the actual
501506 // data in each bin)
502- binStart -= ONEDAY / 2 ;
507+ binStart -= HALFDAY ;
503508 }
504509 var nextBinStart = axes . tickIncrement ( binStart , dtick ) ;
505510
@@ -698,22 +703,28 @@ axes.calcTicks = function calcTicks(ax, opts) {
698703 var maxRange = Math . max ( rng [ 0 ] , rng [ 1 ] ) ;
699704
700705 var definedDelta ;
701- if ( isPeriod && ax . tickformat ) {
706+ var tickformat = axes . getTickFormat ( ax ) ;
707+ if ( isPeriod && tickformat ) {
702708 if (
703- ! ( / % [ f L Q s S M H I p X ] / . test ( ax . tickformat ) )
709+ ! ( / % [ f L Q s S M X ] / . test ( tickformat ) )
704710 // %f: microseconds as a decimal number [000000, 999999]
705711 // %L: milliseconds as a decimal number [000, 999]
706712 // %Q: milliseconds since UNIX epoch
707713 // %s: seconds since UNIX epoch
708714 // %S: second as a decimal number [00,61]
709715 // %M: minute as a decimal number [00,59]
710- // %H: hour (24-hour clock) as a decimal number [00,23]
711- // %I: hour (12-hour clock) as a decimal number [01,12]
712- // %p: either AM or PM
713716 // %X: the locale’s time, such as %-I:%M:%S %p
714717 ) {
715718 if (
716- / % [ A a d e j u w x ] / . test ( ax . tickformat )
719+ / % [ H I ] / . test ( tickformat )
720+ // %H: hour (24-hour clock) as a decimal number [00,23]
721+ // %I: hour (12-hour clock) as a decimal number [01,12]
722+ ) definedDelta = ONEHOUR ;
723+ else if (
724+ / % p / . test ( tickformat ) // %p: either AM or PM
725+ ) definedDelta = HALFDAY ;
726+ else if (
727+ / % [ A a d e j u w x ] / . test ( tickformat )
717728 // %A: full weekday name
718729 // %a: abbreviated weekday name
719730 // %d: zero-padded day of the month as a decimal number [01,31]
@@ -724,75 +735,124 @@ axes.calcTicks = function calcTicks(ax, opts) {
724735 // %x: the locale’s date, such as %-m/%-d/%Y
725736 ) definedDelta = ONEDAY ;
726737 else if (
727- / % [ U V W ] / . test ( ax . tickformat )
738+ / % [ U V W ] / . test ( tickformat )
728739 // %U: Sunday-based week of the year as a decimal number [00,53]
729740 // %V: ISO 8601 week of the year as a decimal number [01, 53]
730741 // %W: Monday-based week of the year as a decimal number [00,53]
731742 ) definedDelta = ONEWEEK ;
732743 else if (
733- / % [ B b m ] / . test ( ax . tickformat )
744+ / % [ B b m ] / . test ( tickformat )
734745 // %B: full month name
735746 // %b: abbreviated month name
736747 // %m: month as a decimal number [01,12]
737748 ) definedDelta = ONEAVGMONTH ;
738749 else if (
739- / % [ q ] / . test ( ax . tickformat )
750+ / % [ q ] / . test ( tickformat )
740751 // %q: quarter of the year as a decimal number [1,4]
741752 ) definedDelta = ONEAVGQUARTER ;
742753 else if (
743- / % [ Y y ] / . test ( ax . tickformat )
754+ / % [ Y y ] / . test ( tickformat )
744755 // %Y: year with century as a decimal number, such as 1999
745756 // %y: year without century as a decimal number [00,99]
746757 ) definedDelta = ONEAVGYEAR ;
747758 }
748759 }
749760
750- var removedPreTick0Label = false ;
751- var ticksOut = new Array ( tickVals . length ) ;
761+ var ticksOut = [ ] ;
752762 var i ;
763+ var prevText ;
753764 for ( i = 0 ; i < tickVals . length ; i ++ ) {
754765 var _minor = tickVals [ i ] . minor ;
755766 var _value = tickVals [ i ] . value ;
756767
757- ticksOut [ i ] = axes . tickText (
768+ var t = axes . tickText (
758769 ax ,
759770 _value ,
760771 false , // hover
761772 _minor // noSuffixPrefix
762773 ) ;
763774
764- if ( isPeriod ) {
765- var v = tickVals [ i ] . value ;
775+ if ( isPeriod && prevText === t . text ) continue ;
776+ prevText = t . text ;
777+
778+ ticksOut . push ( t ) ;
779+ }
780+
781+ if ( isPeriod ) {
782+ var removedPreTick0Label = false ;
783+
784+ for ( i = 0 ; i < ticksOut . length ; i ++ ) {
785+ var v = ticksOut [ i ] . x ;
766786
767787 var a = i ;
768788 var b = i + 1 ;
769- if ( i < tickVals . length - 1 ) {
789+ if ( i < ticksOut . length - 1 ) {
770790 a = i ;
771791 b = i + 1 ;
772- } else {
792+ } else if ( i > 0 ) {
773793 a = i - 1 ;
774794 b = i ;
795+ } else {
796+ a = i ;
797+ b = i ;
775798 }
776799
777- var A = tickVals [ a ] . value ;
778- var B = tickVals [ b ] . value ;
800+ var A = ticksOut [ a ] . x ;
801+ var B = ticksOut [ b ] . x ;
802+ var actualDelta = Math . abs ( B - A ) ;
803+ var delta = definedDelta || actualDelta ;
804+ var periodLength = 0 ;
779805
780- var delta = definedDelta || Math . abs ( B - A ) ;
781- if ( delta >= ONEDAY * 365 ) { // Years could have days less than ONEAVGYEAR period
782- v += ONEAVGYEAR / 2 ;
783- } else if ( delta >= ONEAVGQUARTER ) {
784- v += ONEAVGQUARTER / 2 ;
785- } else if ( delta >= ONEMINMONTH ) { // Months could have days less than ONEAVGMONTH period
786- var actualDelta = Math . abs ( B - A ) ;
806+ if ( delta >= ONEMINYEAR ) {
807+ if ( actualDelta >= ONEMINYEAR && actualDelta <= ONEMAXYEAR ) {
808+ periodLength = actualDelta ;
809+ } else {
810+ periodLength = ONEAVGYEAR ;
811+ }
812+ } else if ( definedDelta === ONEAVGQUARTER && delta >= ONEMINQUARTER ) {
813+ if ( actualDelta >= ONEMINQUARTER && actualDelta <= ONEMAXQUARTER ) {
814+ periodLength = actualDelta ;
815+ } else {
816+ periodLength = ONEAVGQUARTER ;
817+ }
818+ } else if ( delta >= ONEMINMONTH ) {
787819 if ( actualDelta >= ONEMINMONTH && actualDelta <= ONEMAXMONTH ) {
788- v + = actualDelta / 2 ;
820+ periodLength = actualDelta ;
789821 } else {
790- v + = ONEAVGMONTH / 2 ;
822+ periodLength = ONEAVGMONTH ;
791823 }
792- } else if ( delta >= ONEWEEK ) {
793- v + = ONEWEEK / 2 ;
824+ } else if ( definedDelta === ONEWEEK && delta >= ONEWEEK ) {
825+ periodLength = ONEWEEK ;
794826 } else if ( delta >= ONEDAY ) {
795- v += ONEDAY / 2 ;
827+ periodLength = ONEDAY ;
828+ } else if ( definedDelta === HALFDAY && delta >= HALFDAY ) {
829+ periodLength = HALFDAY ;
830+ } else if ( definedDelta === ONEHOUR && delta >= ONEHOUR ) {
831+ periodLength = ONEHOUR ;
832+ }
833+
834+ if ( periodLength && ax . rangebreaks ) {
835+ var nFirstHalf = 0 ;
836+ var nSecondHalf = 0 ;
837+ var nAll = 2 * 3 * 7 ; // number of samples
838+ for ( var c = 0 ; c < nAll ; c ++ ) {
839+ var r = c / nAll ;
840+ if ( ax . maskBreaks ( A * ( 1 - r ) + B * r ) !== BADNUM ) {
841+ if ( r < 0.5 ) {
842+ nFirstHalf ++ ;
843+ } else {
844+ nSecondHalf ++ ;
845+ }
846+ }
847+ }
848+
849+ if ( nSecondHalf ) {
850+ periodLength *= ( nFirstHalf + nSecondHalf ) / nAll ;
851+ }
852+ }
853+
854+ if ( periodLength <= actualDelta ) { // i.e. to ensure new label positions remain between ticks
855+ v += periodLength / 2 ;
796856 }
797857
798858 ticksOut [ i ] . periodX = v ;
@@ -802,15 +862,15 @@ axes.calcTicks = function calcTicks(ax, opts) {
802862 removedPreTick0Label = true ;
803863 }
804864 }
805- }
806865
807- if ( removedPreTick0Label ) {
808- for ( i = 0 ; i < ticksOut . length ; i ++ ) {
809- if ( ticksOut [ i ] . periodX <= maxRange && ticksOut [ i ] . periodX >= minRange ) {
810- // redo first visible tick
811- ax . _prevDateHead = '' ;
812- ticksOut [ i ] . text = axes . tickText ( ax , tickVals [ i ] . value ) . text ;
813- break ;
866+ if ( removedPreTick0Label ) {
867+ for ( i = 0 ; i < ticksOut . length ; i ++ ) {
868+ if ( ticksOut [ i ] . periodX <= maxRange && ticksOut [ i ] . periodX >= minRange ) {
869+ // redo first visible tick
870+ ax . _prevDateHead = '' ;
871+ ticksOut [ i ] . text = axes . tickText ( ax , ticksOut [ i ] . x ) . text ;
872+ break ;
873+ }
814874 }
815875 }
816876 }
@@ -924,7 +984,8 @@ axes.autoTicks = function(ax, roughDTick) {
924984 // 2 or 3 days... but that's a weird enough case that we'll ignore it.
925985 ax . tick0 = Lib . dateTick0 ( ax . calendar , true ) ;
926986
927- if ( / % [ u V W ] / . test ( ax . tickformat ) ) {
987+ var tickformat = axes . getTickFormat ( ax ) ;
988+ if ( / % [ u V W ] / . test ( tickformat ) ) {
928989 // replace Sunday with Monday for ISO and Monday-based formats
929990 var len = ax . tick0 . length ;
930991 var lastD = + ax . tick0 [ len - 1 ] ;
0 commit comments