@@ -22,6 +22,10 @@ var numConstants = require('../../constants/numerical');
22
22
var FP_SAFE = numConstants . FP_SAFE ;
23
23
var BADNUM = numConstants . BADNUM ;
24
24
var 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 ;
25
29
26
30
var constants = require ( './constants' ) ;
27
31
var axisIds = require ( './axis_ids' ) ;
@@ -170,14 +174,73 @@ module.exports = function setConvert(ax, fullLayout) {
170
174
if ( isNumeric ( v ) ) return + v ;
171
175
}
172
176
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 ) {
174
183
if ( ! isNumeric ( v ) ) return BADNUM ;
184
+ return _l2p ( v , ax . _m , ax . _b ) ;
185
+ } ;
175
186
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
+ } ;
179
190
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
+ }
181
244
182
245
// conversions among c/l/p are fairly simple - do them together for all axis types
183
246
ax . c2l = ( ax . type === 'log' ) ? toLog : ensureNumber ;
@@ -486,6 +549,51 @@ module.exports = function setConvert(ax, fullLayout) {
486
549
ax . _b = - ax . _m * rl0 ;
487
550
}
488
551
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
+
489
597
if ( ! isFinite ( ax . _m ) || ! isFinite ( ax . _b ) || ax . _length < 0 ) {
490
598
fullLayout . _replotting = false ;
491
599
throw new Error ( 'Something went wrong with axis scaling' ) ;
@@ -565,6 +673,141 @@ module.exports = function setConvert(ax, fullLayout) {
565
673
}
566
674
return v ;
567
675
} ;
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
+
568
811
// makeCalcdata: takes an x or y array and converts it
569
812
// to a position on the axis object "ax"
570
813
// inputs:
0 commit comments