@@ -20,6 +20,9 @@ var Lib = require('../../lib');
20
20
var svgTextUtils = require ( '../../lib/svg_text_utils' ) ;
21
21
22
22
var xmlnsNamespaces = require ( '../../constants/xmlns_namespaces' ) ;
23
+ var alignment = require ( '../../constants/alignment' ) ;
24
+ var LINE_SPACING = alignment . LINE_SPACING ;
25
+
23
26
var subTypes = require ( '../../traces/scatter/subtypes' ) ;
24
27
var makeBubbleSizeFn = require ( '../../traces/scatter/make_bubble_size_func' ) ;
25
28
@@ -41,6 +44,12 @@ drawing.font = function(s, family, size, color) {
41
44
if ( color ) s . call ( Color . fill , color ) ;
42
45
} ;
43
46
47
+ /*
48
+ * Positioning helpers
49
+ * Note: do not use `setPosition` with <text> nodes modified by
50
+ * `svgTextUtils.convertToTspans`. Use `svgTextUtils.positionText`
51
+ * instead, so that <tspan.line> elements get updated to match.
52
+ */
44
53
drawing . setPosition = function ( s , x , y ) { s . attr ( 'x' , x ) . attr ( 'y' , y ) ; } ;
45
54
drawing . setSize = function ( s , w , h ) { s . attr ( 'width' , w ) . attr ( 'height' , h ) ; } ;
46
55
drawing . setRect = function ( s , x , y , w , h ) {
@@ -420,8 +429,7 @@ drawing.tryColorscale = function(marker, prefix) {
420
429
} ;
421
430
422
431
// draw text at points
423
- var TEXTOFFSETSIGN = { start : 1 , end : - 1 , middle : 0 , bottom : 1 , top : - 1 } ,
424
- LINEEXPAND = 1.3 ;
432
+ var TEXTOFFSETSIGN = { start : 1 , end : - 1 , middle : 0 , bottom : 1 , top : - 1 } ;
425
433
drawing . textPointStyle = function ( s , trace , gd ) {
426
434
s . each ( function ( d ) {
427
435
var p = d3 . select ( this ) ,
@@ -454,20 +462,15 @@ drawing.textPointStyle = function(s, trace, gd) {
454
462
. attr ( 'text-anchor' , h )
455
463
. text ( text )
456
464
. call ( svgTextUtils . convertToTspans , gd ) ;
457
- var pgroup = d3 . select ( this . parentNode ) ,
458
- tspans = p . selectAll ( 'tspan.line' ) ,
459
- numLines = ( ( tspans [ 0 ] . length || 1 ) - 1 ) * LINEEXPAND + 1 ,
460
- dx = TEXTOFFSETSIGN [ h ] * r ,
461
- dy = fontSize * 0.75 + TEXTOFFSETSIGN [ v ] * r +
465
+
466
+ var pgroup = d3 . select ( this . parentNode ) ;
467
+ var numLines = ( svgTextUtils . lineCount ( p ) - 1 ) * LINE_SPACING + 1 ;
468
+ var dx = TEXTOFFSETSIGN [ h ] * r ;
469
+ var dy = fontSize * 0.75 + TEXTOFFSETSIGN [ v ] * r +
462
470
( TEXTOFFSETSIGN [ v ] - 1 ) * numLines * fontSize / 2 ;
463
471
464
472
// fix the overall text group position
465
473
pgroup . attr ( 'transform' , 'translate(' + dx + ',' + dy + ')' ) ;
466
-
467
- // then fix multiline text
468
- if ( numLines > 1 ) {
469
- tspans . attr ( { x : p . attr ( 'x' ) , y : p . attr ( 'y' ) } ) ;
470
- }
471
474
} ) ;
472
475
} ;
473
476
@@ -606,33 +609,87 @@ drawing.makeTester = function() {
606
609
drawing . testref = testref ;
607
610
} ;
608
611
609
- // use our offscreen tester to get a clientRect for an element,
610
- // in a reference frame where it isn't translated and its anchor
611
- // point is at (0,0)
612
- // always returns a copy of the bbox, so the caller can modify it safely
612
+ /*
613
+ * use our offscreen tester to get a clientRect for an element,
614
+ * in a reference frame where it isn't translated and its anchor
615
+ * point is at (0,0)
616
+ * always returns a copy of the bbox, so the caller can modify it safely
617
+ */
613
618
drawing . savedBBoxes = { } ;
614
619
var savedBBoxesCount = 0 ;
615
620
var maxSavedBBoxes = 10000 ;
616
621
617
- drawing . bBox = function ( node ) {
618
- // cache elements we've already measured so we don't have to
619
- // remeasure the same thing many times
620
- var hash = nodeHash ( node ) ;
621
- var out = drawing . savedBBoxes [ hash ] ;
622
- if ( out ) return Lib . extendFlat ( { } , out ) ;
622
+ drawing . bBox = function ( node , hash ) {
623
+ /*
624
+ * Cache elements we've already measured so we don't have to
625
+ * remeasure the same thing many times
626
+ * We have a few bBox callers though who pass a node larger than
627
+ * a <text> or a MathJax <g>, such as an axis group containing many labels.
628
+ * These will not generate a hash (unless we figure out an appropriate
629
+ * hash key for them) and thus we will not hash them.
630
+ */
631
+ if ( ! hash ) hash = nodeHash ( node ) ;
632
+ var out ;
633
+ if ( hash ) {
634
+ out = drawing . savedBBoxes [ hash ] ;
635
+ if ( out ) return Lib . extendFlat ( { } , out ) ;
636
+ }
637
+ else if ( node . children . length === 1 ) {
638
+ /*
639
+ * If we have only one child element, which is itself hashable, make
640
+ * a new hash from this element plus its x,y,transform
641
+ * These bounding boxes *include* x,y,transform - mostly for use by
642
+ * callers trying to avoid overlaps (ie titles)
643
+ */
644
+ var innerNode = node . children [ 0 ] ;
645
+
646
+ hash = nodeHash ( innerNode ) ;
647
+ if ( hash ) {
648
+ var x = + innerNode . getAttribute ( 'x' ) || 0 ;
649
+ var y = + innerNode . getAttribute ( 'y' ) || 0 ;
650
+ var transform = innerNode . getAttribute ( 'transform' ) ;
651
+
652
+ if ( ! transform ) {
653
+ // in this case, just varying x and y, don't bother caching
654
+ // the final bBox because the alteration is quick.
655
+ var innerBB = drawing . bBox ( innerNode , hash ) ;
656
+ if ( x ) {
657
+ innerBB . left += x ;
658
+ innerBB . right += x ;
659
+ }
660
+ if ( y ) {
661
+ innerBB . top += y ;
662
+ innerBB . bottom += y ;
663
+ }
664
+ return innerBB ;
665
+ }
666
+ /*
667
+ * else we have a transform - rather than make a complicated
668
+ * (and error-prone and probably slow) transform parser/calculator,
669
+ * just continue on calculating the boundingClientRect of the group
670
+ * and use the new composite hash to cache it.
671
+ * That said, `innerNode.transform.baseVal` is an array of
672
+ * `SVGTransform` objects, that *do* seem to have a nice matrix
673
+ * multiplication interface that we could use to avoid making
674
+ * another getBoundingClientRect call...
675
+ */
676
+ hash += '~' + x + '~' + y + '~' + transform ;
677
+
678
+ out = drawing . savedBBoxes [ hash ] ;
679
+ if ( out ) return Lib . extendFlat ( { } , out ) ;
680
+ }
681
+ }
623
682
624
683
var tester = drawing . tester . node ( ) ;
625
684
626
685
// copy the node to test into the tester
627
686
var testNode = node . cloneNode ( true ) ;
628
687
tester . appendChild ( testNode ) ;
629
688
630
- // standardize its position... do we really want to do this?
631
- d3 . select ( testNode ) . attr ( {
632
- x : 0 ,
633
- y : 0 ,
634
- transform : ''
635
- } ) ;
689
+ // standardize its position (and newline tspans if any)
690
+ d3 . select ( testNode )
691
+ . attr ( 'transform' , null )
692
+ . call ( svgTextUtils . positionText , 0 , 0 ) ;
636
693
637
694
var testRect = testNode . getBoundingClientRect ( ) ;
638
695
var refRect = drawing . testref
@@ -655,35 +712,23 @@ drawing.bBox = function(node) {
655
712
// by saving boxes for long-gone elements
656
713
if ( savedBBoxesCount >= maxSavedBBoxes ) {
657
714
drawing . savedBBoxes = { } ;
658
- maxSavedBBoxes = 0 ;
715
+ savedBBoxesCount = 0 ;
659
716
}
660
717
661
718
// cache this bbox
662
- drawing . savedBBoxes [ hash ] = bb ;
719
+ if ( hash ) drawing . savedBBoxes [ hash ] = bb ;
663
720
savedBBoxesCount ++ ;
664
721
665
722
return Lib . extendFlat ( { } , bb ) ;
666
723
} ;
667
724
668
725
// capture everything about a node (at least in our usage) that
669
726
// impacts its bounding box, given that bBox clears x, y, and transform
670
- // TODO: is this really everything? Is it worth taking only parts of style,
671
- // so we can share across more changes (like colors)? I guess we can't strip
672
- // colors and stuff from inside innerHTML so maybe not worth bothering outside.
673
- // TODO # 2: this can be long, so could take a lot of memory, do we want to
674
- // hash it? But that can be slow...
675
- // extracting this string from a typical element takes ~3 microsec, where
676
- // doing a simple hash ala https://stackoverflow.com/questions/7616461
677
- // adds ~15 microsec (nearly all of this is spent in charCodeAt)
678
- // function hash(s) {
679
- // var h = 0;
680
- // for (var i = 0; i < s.length; i++) {
681
- // h = (((h << 5) - h) + s.charCodeAt(i)) | 0; // codePointAt?
682
- // }
683
- // return h;
684
- // }
685
727
function nodeHash ( node ) {
686
- return node . innerHTML +
728
+ var inputText = node . getAttribute ( 'data-unformatted' ) ;
729
+ if ( inputText === null ) return ;
730
+ return inputText +
731
+ node . getAttribute ( 'data-math' ) +
687
732
node . getAttribute ( 'text-anchor' ) +
688
733
node . getAttribute ( 'style' ) ;
689
734
}
@@ -841,13 +886,3 @@ drawing.setTextPointsScale = function(selection, xScale, yScale) {
841
886
el . attr ( 'transform' , transforms . join ( ' ' ) ) ;
842
887
} ) ;
843
888
} ;
844
-
845
- drawing . measureText = function ( tester , text , font ) {
846
- var dummyText = tester . append ( 'text' )
847
- . text ( text )
848
- . call ( drawing . font , font ) ;
849
-
850
- var bbox = drawing . bBox ( dummyText . node ( ) ) ;
851
- dummyText . remove ( ) ;
852
- return bbox ;
853
- } ;
0 commit comments