Skip to content

Commit 3e83252

Browse files
authored
Merge pull request #1792 from plotly/no-innerHTML
No innerHTML and pseudo-html cleanup
2 parents a227ae1 + 8e3d4fa commit 3e83252

38 files changed

+493
-345
lines changed

src/components/annotations/draw.js

+5-15
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
203203

204204
var annText = annTextGroupInner.append('text')
205205
.classed('annotation-text', true)
206-
.attr('data-unformatted', options.text)
207206
.text(options.text);
208207

209208
function textLayout(s) {
@@ -232,11 +231,6 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
232231
wholeLink.node().appendChild(annTextBG.node());
233232
}
234233

235-
236-
// make sure lines are aligned the way they will be
237-
// at the end, even if their position changes
238-
annText.selectAll('tspan.line').attr({y: 0, x: 0});
239-
240234
var mathjaxGroup = annTextGroupInner.select('.annotation-text-math-group');
241235
var hasMathjax = !mathjaxGroup.empty();
242236
var anntextBB = Drawing.bBox(
@@ -423,14 +417,11 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
423417
.call(Drawing.setClipUrl, isSizeConstrained ? annClipID : null);
424418
}
425419
else {
426-
var texty = borderfull + yShift - anntextBB.top,
427-
textx = borderfull + xShift - anntextBB.left;
428-
annText.attr({
429-
x: textx,
430-
y: texty
431-
})
432-
.call(Drawing.setClipUrl, isSizeConstrained ? annClipID : null);
433-
annText.selectAll('tspan.line').attr({y: texty, x: textx});
420+
var texty = borderfull + yShift - anntextBB.top;
421+
var textx = borderfull + xShift - anntextBB.left;
422+
423+
annText.call(svgTextUtils.positionText, textx, texty)
424+
.call(Drawing.setClipUrl, isSizeConstrained ? annClipID : null);
434425
}
435426

436427
annTextClip.select('rect').call(Drawing.setRect, borderfull, borderfull,
@@ -693,7 +684,6 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
693684
.call(textLayout)
694685
.on('edit', function(_text) {
695686
options.text = _text;
696-
this.attr({'data-unformatted': options.text});
697687
this.call(textLayout);
698688

699689
var update = {};

src/components/annotations/draw_arrow_head.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ module.exports = function drawArrowHead(el3, style, ends, mag, standoff) {
111111
function drawhead(p, rot) {
112112
if(!headStyle.path) return;
113113
if(style > 5) rot = 0; // don't rotate square or circle
114-
d3.select(el.parentElement).append('path')
114+
d3.select(el.parentNode).append('path')
115115
.attr({
116116
'class': el3.attr('class'),
117117
d: headStyle.path,

src/components/colorbar/draw.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ var setCursor = require('../../lib/setcursor');
2323
var Drawing = require('../drawing');
2424
var Color = require('../color');
2525
var Titles = require('../titles');
26+
var svgTextUtils = require('../../lib/svg_text_utils');
27+
var LINE_SPACING = require('../../constants/alignment').LINE_SPACING;
2628

2729
var handleAxisDefaults = require('../../plots/cartesian/axis_defaults');
2830
var handleAxisPositionDefaults = require('../../plots/cartesian/position_defaults');
@@ -296,7 +298,7 @@ module.exports = function draw(gd, id) {
296298
lineSize = 15.6;
297299
if(titleText.node()) {
298300
lineSize =
299-
parseInt(titleText.style('font-size'), 10) * 1.3;
301+
parseInt(titleText.style('font-size'), 10) * LINE_SPACING;
300302
}
301303
if(mathJaxNode) {
302304
titleHeight = Drawing.bBox(mathJaxNode).height;
@@ -308,8 +310,7 @@ module.exports = function draw(gd, id) {
308310
}
309311
else if(titleText.node() &&
310312
!titleText.classed('js-placeholder')) {
311-
titleHeight = Drawing.bBox(
312-
titleGroup.node()).height;
313+
titleHeight = Drawing.bBox(titleText.node()).height;
313314
}
314315
if(titleHeight) {
315316
// buffer btwn colorbar and title
@@ -322,8 +323,7 @@ module.exports = function draw(gd, id) {
322323
}
323324
else {
324325
cbAxisOut.domain[0] += titleHeight / gs.h;
325-
var nlines = Math.max(1,
326-
titleText.selectAll('tspan.line').size());
326+
var nlines = svgTextUtils.lineCount(titleText);
327327
titleTrans[1] += (1 - nlines) * lineSize;
328328
}
329329

src/components/drawing/index.js

+91-56
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ var Lib = require('../../lib');
2020
var svgTextUtils = require('../../lib/svg_text_utils');
2121

2222
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
23+
var alignment = require('../../constants/alignment');
24+
var LINE_SPACING = alignment.LINE_SPACING;
25+
2326
var subTypes = require('../../traces/scatter/subtypes');
2427
var 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+
*/
4453
drawing.setPosition = function(s, x, y) { s.attr('x', x).attr('y', y); };
4554
drawing.setSize = function(s, w, h) { s.attr('width', w).attr('height', h); };
4655
drawing.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};
425433
drawing.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+
*/
613618
drawing.savedBBoxes = {};
614619
var savedBBoxesCount = 0;
615620
var 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-
// }
685727
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') +
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

Comments
 (0)