@@ -22,6 +22,10 @@ var numConstants = require('../../constants/numerical');
2222var FP_SAFE = numConstants . FP_SAFE ;
2323var BADNUM = numConstants . BADNUM ;
2424var LOG_CLIP = numConstants . LOG_CLIP ;
25+ var ONEDAY = numConstants . ONEDAY ;
26+ var ONEHOUR = numConstants . ONEHOUR ;
27+ var ONEMIN = numConstants . ONEMIN ;
28+ var ONESEC = numConstants . ONESEC ;
2529
2630var constants = require ( './constants' ) ;
2731var axisIds = require ( './axis_ids' ) ;
@@ -170,14 +174,73 @@ module.exports = function setConvert(ax, fullLayout) {
170174 if ( isNumeric ( v ) ) return + v ;
171175 }
172176
173- function l2p ( v ) {
177+ // include 2 fractional digits on pixel, for PDF zooming etc
178+ function _l2p ( v , m , b ) { return d3 . round ( b + m * v , 2 ) ; }
179+
180+ function _p2l ( px , m , b ) { return ( px - b ) / m ; }
181+
182+ var l2p = function l2p ( v ) {
174183 if ( ! isNumeric ( v ) ) return BADNUM ;
184+ return _l2p ( v , ax . _m , ax . _b ) ;
185+ } ;
175186
176- // include 2 fractional digits on pixel, for PDF zooming etc
177- return d3 . round ( ax . _b + ax . _m * v , 2 ) ;
178- }
187+ var p2l = function ( px ) {
188+ return _p2l ( px , ax . _m , ax . _b ) ;
189+ } ;
179190
180- function p2l ( px ) { return ( px - ax . _b ) / ax . _m ; }
191+ if ( ax . breaks ) {
192+ if ( axLetter === 'y' ) {
193+ l2p = function ( v ) {
194+ if ( ! isNumeric ( v ) ) return BADNUM ;
195+ if ( ! ax . _breaks . length ) return _l2p ( v , ax . _m , ax . _b ) ;
196+
197+ var b = ax . _B [ 0 ] ;
198+ for ( var i = 0 ; i < ax . _breaks . length ; i ++ ) {
199+ var brk = ax . _breaks [ i ] ;
200+ if ( v <= brk . min ) b = ax . _B [ i + 1 ] ;
201+ else if ( v > brk . max ) break ;
202+ }
203+ return _l2p ( v , - ax . _m2 , b ) ;
204+ } ;
205+ p2l = function ( px ) {
206+ if ( ! isNumeric ( px ) ) return BADNUM ;
207+ if ( ! ax . _breaks . length ) return _p2l ( px , ax . _m , ax . _b ) ;
208+
209+ var b = ax . _B [ 0 ] ;
210+ for ( var i = 0 ; i < ax . _breaks . length ; i ++ ) {
211+ var brk = ax . _breaks [ i ] ;
212+ if ( px >= brk . pmin ) b = ax . _B [ i + 1 ] ;
213+ else if ( px < brk . pmax ) break ;
214+ }
215+ return _p2l ( px , - ax . _m2 , b ) ;
216+ } ;
217+ } else {
218+ l2p = function ( v ) {
219+ if ( ! isNumeric ( v ) ) return BADNUM ;
220+ if ( ! ax . _breaks . length ) return _l2p ( v , ax . _m , ax . _b ) ;
221+
222+ var b = ax . _B [ 0 ] ;
223+ for ( var i = 0 ; i < ax . _breaks . length ; i ++ ) {
224+ var brk = ax . _breaks [ i ] ;
225+ if ( v >= brk . max ) b = ax . _B [ i + 1 ] ;
226+ else if ( v < brk . min ) break ;
227+ }
228+ return _l2p ( v , ax . _m2 , b ) ;
229+ } ;
230+ p2l = function ( px ) {
231+ if ( ! isNumeric ( px ) ) return BADNUM ;
232+ if ( ! ax . _breaks . length ) return _p2l ( px , ax . _m , ax . _b ) ;
233+
234+ var b = ax . _B [ 0 ] ;
235+ for ( var i = 0 ; i < ax . _breaks . length ; i ++ ) {
236+ var brk = ax . _breaks [ i ] ;
237+ if ( px >= brk . pmax ) b = ax . _B [ i + 1 ] ;
238+ else if ( px < brk . pmin ) break ;
239+ }
240+ return _p2l ( px , ax . _m2 , b ) ;
241+ } ;
242+ }
243+ }
181244
182245 // conversions among c/l/p are fairly simple - do them together for all axis types
183246 ax . c2l = ( ax . type === 'log' ) ? toLog : ensureNumber ;
@@ -486,6 +549,51 @@ module.exports = function setConvert(ax, fullLayout) {
486549 ax . _b = - ax . _m * rl0 ;
487550 }
488551
552+ // set of "N" disjoint breaks inside the range
553+ ax . _breaks = [ ] ;
554+ // length of these breaks in value space
555+ ax . _lBreaks = 0 ;
556+ // l2p slope (same for all intervals)
557+ ax . _m2 = 0 ;
558+ // set of l2p offsets (one for each of the (N+1) piecewise intervals)
559+ ax . _B = [ ] ;
560+
561+ if ( ax . breaks ) {
562+ var i , brk ;
563+
564+ ax . _breaks = ax . locateBreaks ( rl0 , rl1 ) ;
565+
566+ if ( ax . _breaks . length ) {
567+ for ( i = 0 ; i < ax . _breaks . length ; i ++ ) {
568+ brk = ax . _breaks [ i ] ;
569+ ax . _lBreaks += ( brk . max - brk . min ) ;
570+ }
571+
572+ ax . _m2 = ax . _length / ( rl1 - rl0 - ax . _lBreaks ) ;
573+
574+ if ( axLetter === 'y' ) {
575+ ax . _breaks . reverse ( ) ;
576+ // N.B. top to bottom (negative coord, positive px direction)
577+ ax . _B . push ( ax . _m2 * rl1 ) ;
578+ } else {
579+ ax . _B . push ( - ax . _m2 * rl0 ) ;
580+ }
581+
582+ for ( i = 0 ; i < ax . _breaks . length ; i ++ ) {
583+ brk = ax . _breaks [ i ] ;
584+ ax . _B . push ( ax . _B [ ax . _B . length - 1 ] - ax . _m2 * ( brk . max - brk . min ) ) ;
585+ }
586+
587+ // fill pixel (i.e. 'p') min/max here,
588+ // to not have to loop through the _breaks twice during `p2l`
589+ for ( i = 0 ; i < ax . _breaks . length ; i ++ ) {
590+ brk = ax . _breaks [ i ] ;
591+ brk . pmin = l2p ( brk . min ) ;
592+ brk . pmax = l2p ( brk . max ) ;
593+ }
594+ }
595+ }
596+
489597 if ( ! isFinite ( ax . _m ) || ! isFinite ( ax . _b ) || ax . _length < 0 ) {
490598 fullLayout . _replotting = false ;
491599 throw new Error ( 'Something went wrong with axis scaling' ) ;
@@ -565,6 +673,141 @@ module.exports = function setConvert(ax, fullLayout) {
565673 }
566674 return v ;
567675 } ;
676+
677+ ax . locateBreaks = function ( r0 , r1 ) {
678+ var i , bnds , b0 , b1 ;
679+
680+ var breaksOut = [ ] ;
681+ if ( ! ax . breaks ) return breaksOut ;
682+
683+ var breaksIn ;
684+ if ( ax . type === 'date' ) {
685+ breaksIn = ax . breaks . slice ( ) . sort ( function ( a , b ) {
686+ if ( a . pattern === '%w' && b . pattern === '%H' ) return - 1 ;
687+ else if ( b . pattern === '%w' && a . pattern === '%H' ) return 1 ;
688+ return 0 ;
689+ } ) ;
690+ } else {
691+ breaksIn = ax . breaks ;
692+ }
693+
694+ var addBreak = function ( min , max ) {
695+ min = Lib . constrain ( min , r0 , r1 ) ;
696+ max = Lib . constrain ( max , r0 , r1 ) ;
697+ if ( min === max ) return ;
698+
699+ var isNewBreak = true ;
700+ for ( var j = 0 ; j < breaksOut . length ; j ++ ) {
701+ var brkj = breaksOut [ j ] ;
702+ if ( min > brkj . max || max < brkj . min ) {
703+ // potentially a new break
704+ } else {
705+ if ( min < brkj . min ) {
706+ brkj . min = min ;
707+ }
708+ if ( max > brkj . max ) {
709+ brkj . max = max ;
710+ }
711+ isNewBreak = false ;
712+ }
713+ }
714+ if ( isNewBreak ) {
715+ breaksOut . push ( { min : min , max : max } ) ;
716+ }
717+ } ;
718+
719+ for ( i = 0 ; i < breaksIn . length ; i ++ ) {
720+ var brk = breaksIn [ i ] ;
721+
722+ if ( brk . enabled ) {
723+ var op = brk . operation ;
724+ var op0 = op . charAt ( 0 ) ;
725+ var op1 = op . charAt ( 1 ) ;
726+
727+ if ( brk . bounds ) {
728+ if ( brk . pattern ) {
729+ bnds = Lib . simpleMap ( brk . bounds , cleanNumber ) ;
730+ if ( bnds [ 0 ] === bnds [ 1 ] && op === '()' ) continue ;
731+
732+ // r0 value as date
733+ var r0Date = new Date ( r0 ) ;
734+ // r0 value for break pattern
735+ var r0Pattern ;
736+ // delta between r0 and first break in break pattern values
737+ var r0PatternDelta ;
738+ // delta between break bounds in ms
739+ var bndDelta ;
740+ // step in ms between breaks
741+ var step ;
742+ // tracker to position bounds
743+ var t ;
744+
745+ switch ( brk . pattern ) {
746+ case '%w' :
747+ b0 = bnds [ 0 ] + ( op0 === '(' ? 1 : 0 ) ;
748+ b1 = bnds [ 1 ] ;
749+ r0Pattern = r0Date . getUTCDay ( ) ;
750+ r0PatternDelta = b0 - r0Pattern ;
751+ bndDelta = ( b1 >= b0 ? b1 - b0 : ( b1 + 7 ) - b0 ) * ONEDAY ;
752+ if ( op1 === ']' ) bndDelta += ONEDAY ;
753+ step = 7 * ONEDAY ;
754+
755+ t = r0 + r0PatternDelta * ONEDAY -
756+ r0Date . getUTCHours ( ) * ONEHOUR -
757+ r0Date . getUTCMinutes ( ) * ONEMIN -
758+ r0Date . getUTCSeconds ( ) * ONESEC -
759+ r0Date . getUTCMilliseconds ( ) ;
760+ break ;
761+ case '%H' :
762+ b0 = bnds [ 0 ] ;
763+ b1 = bnds [ 1 ] ;
764+ r0Pattern = r0Date . getUTCHours ( ) ;
765+ r0PatternDelta = b0 - r0Pattern ;
766+ bndDelta = ( b1 >= b0 ? b1 - b0 : ( b1 + 24 ) - b0 ) * ONEHOUR ;
767+ step = ONEDAY ;
768+
769+ t = r0 + r0PatternDelta * ONEHOUR -
770+ r0Date . getUTCMinutes ( ) * ONEMIN -
771+ r0Date . getUTCSeconds ( ) * ONESEC -
772+ r0Date . getUTCMilliseconds ( ) ;
773+ break ;
774+ }
775+
776+ while ( t <= r1 ) {
777+ // TODO we need to remove decimal (most often found
778+ // in auto ranges) for this to work correctly,
779+ // should this be Math.floor, Math.ceil or
780+ // Math.round ??
781+ addBreak ( Math . floor ( t ) , Math . floor ( t + bndDelta ) ) ;
782+ t += step ;
783+ }
784+ } else {
785+ bnds = Lib . simpleMap ( brk . bounds , ax . r2l ) ;
786+ if ( bnds [ 0 ] <= bnds [ 1 ] ) {
787+ b0 = bnds [ 0 ] ;
788+ b1 = bnds [ 1 ] ;
789+ } else {
790+ b0 = bnds [ 1 ] ;
791+ b1 = bnds [ 0 ] ;
792+ }
793+ addBreak ( b0 , b1 ) ;
794+ }
795+ } else {
796+ var vals = Lib . simpleMap ( brk . values , ax . d2c ) ;
797+ for ( var j = 0 ; j < vals . length ; j ++ ) {
798+ b0 = vals [ j ] ;
799+ b1 = b0 + brk . dvalue ;
800+ addBreak ( b0 , b1 ) ;
801+ }
802+ }
803+ }
804+ }
805+
806+ breaksOut . sort ( function ( a , b ) { return a . min - b . min ; } ) ;
807+
808+ return breaksOut ;
809+ } ;
810+
568811 // makeCalcdata: takes an x or y array and converts it
569812 // to a position on the axis object "ax"
570813 // inputs:
0 commit comments