@@ -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