@@ -20,6 +20,9 @@ var Lib = require('../../lib');
2020var svgTextUtils = require ( '../../lib/svg_text_utils' ) ;
2121
2222var xmlnsNamespaces = require ( '../../constants/xmlns_namespaces' ) ;
23+ var alignment = require ( '../../constants/alignment' ) ;
24+ var LINE_SPACING = alignment . LINE_SPACING ;
25+
2326var subTypes = require ( '../../traces/scatter/subtypes' ) ;
2427var makeBubbleSizeFn = require ( '../../traces/scatter/make_bubble_size_func' ) ;
2528
@@ -41,6 +44,12 @@ drawing.font = function(s, family, size, color) {
4144 if ( color ) s . call ( Color . fill , color ) ;
4245} ;
4346
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+ */
4453drawing . setPosition = function ( s , x , y ) { s . attr ( 'x' , x ) . attr ( 'y' , y ) ; } ;
4554drawing . setSize = function ( s , w , h ) { s . attr ( 'width' , w ) . attr ( 'height' , h ) ; } ;
4655drawing . setRect = function ( s , x , y , w , h ) {
@@ -420,8 +429,7 @@ drawing.tryColorscale = function(marker, prefix) {
420429} ;
421430
422431// 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 } ;
425433drawing . textPointStyle = function ( s , trace , gd ) {
426434 s . each ( function ( d ) {
427435 var p = d3 . select ( this ) ,
@@ -454,20 +462,15 @@ drawing.textPointStyle = function(s, trace, gd) {
454462 . attr ( 'text-anchor' , h )
455463 . text ( text )
456464 . 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 +
462470 ( TEXTOFFSETSIGN [ v ] - 1 ) * numLines * fontSize / 2 ;
463471
464472 // fix the overall text group position
465473 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- }
471474 } ) ;
472475} ;
473476
@@ -606,33 +609,87 @@ drawing.makeTester = function() {
606609 drawing . testref = testref ;
607610} ;
608611
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+ */
613618drawing . savedBBoxes = { } ;
614619var savedBBoxesCount = 0 ;
615620var maxSavedBBoxes = 10000 ;
616621
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+ }
623682
624683 var tester = drawing . tester . node ( ) ;
625684
626685 // copy the node to test into the tester
627686 var testNode = node . cloneNode ( true ) ;
628687 tester . appendChild ( testNode ) ;
629688
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 ) ;
636693
637694 var testRect = testNode . getBoundingClientRect ( ) ;
638695 var refRect = drawing . testref
@@ -655,35 +712,23 @@ drawing.bBox = function(node) {
655712 // by saving boxes for long-gone elements
656713 if ( savedBBoxesCount >= maxSavedBBoxes ) {
657714 drawing . savedBBoxes = { } ;
658- maxSavedBBoxes = 0 ;
715+ savedBBoxesCount = 0 ;
659716 }
660717
661718 // cache this bbox
662- drawing . savedBBoxes [ hash ] = bb ;
719+ if ( hash ) drawing . savedBBoxes [ hash ] = bb ;
663720 savedBBoxesCount ++ ;
664721
665722 return Lib . extendFlat ( { } , bb ) ;
666723} ;
667724
668725// capture everything about a node (at least in our usage) that
669726// 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- // }
685727function 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' ) +
687732 node . getAttribute ( 'text-anchor' ) +
688733 node . getAttribute ( 'style' ) ;
689734}
@@ -841,13 +886,3 @@ drawing.setTextPointsScale = function(selection, xScale, yScale) {
841886 el . attr ( 'transform' , transforms . join ( ' ' ) ) ;
842887 } ) ;
843888} ;
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