From 53cd362fa290978b9d478838d6f86f5e2a791210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Podle=C5=9Bny?= <17722512+mfazer@users.noreply.github.com> Date: Sun, 30 May 2021 23:53:01 +0200 Subject: [PATCH 1/3] Initial commit - first try of the word wrapping --- src/components/annotations/draw.js | 2 +- src/components/legend/draw.js | 2 +- src/components/titles/index.js | 20 +++++++-- src/lib/svg_text_utils.js | 68 ++++++++++++++++++++++++------ src/plots/cartesian/axes.js | 9 +++- src/traces/table/plot.js | 2 +- 6 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index bf1ebf7fbec..deb849cc47f 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -248,7 +248,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { }[options.align] || 'middle' }); - svgTextUtils.convertToTspans(s, gd, drawGraphicalElements); + svgTextUtils.convertToTspans(s, gd, null, drawGraphicalElements); return s; } diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 6b6e783a68b..7e97c29f146 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -496,7 +496,7 @@ function setupTraceToggle(g, gd) { function textLayout(s, g, gd, legendObj, aTitle) { if(legendObj._inHover) s.attr('data-notex', true); // do not process MathJax for unified hover - svgTextUtils.convertToTspans(s, gd, function() { + svgTextUtils.convertToTspans(s, gd, null, function() { computeTextDimensions(g, gd, legendObj, aTitle); }); } diff --git a/src/components/titles/index.js b/src/components/titles/index.js index 3a260108d67..21408ca8255 100644 --- a/src/components/titles/index.js +++ b/src/components/titles/index.js @@ -56,6 +56,8 @@ function draw(gd, titleClass, options) { var attributes = options.attributes; var transform = options.transform; var group = options.containerGroup; + var isAxis = options.isAxis; // Prepare documentation for this + var wrap = options.wrap; var fullLayout = gd._fullLayout; @@ -120,6 +122,7 @@ function draw(gd, titleClass, options) { function drawTitle(titleEl) { var transformVal; + var convertOptions = null; if(transform) { transformVal = ''; @@ -133,6 +136,17 @@ function draw(gd, titleClass, options) { transformVal = null; } + if(isAxis && wrap) { + var axName = options.propContainer._name; + var axOut = gd._fullLayout[axName]; + + convertOptions = { + wrap: wrap, + axisLength: axOut._length, + axisOrientation: axOut._id.substr(0, 1) === 'y' ? 'v' : 'h' + }; + } + titleEl.attr('transform', transformVal); titleEl.style({ @@ -143,13 +157,13 @@ function draw(gd, titleClass, options) { 'font-weight': Plots.fontWeight }) .attr(attributes) - .call(svgTextUtils.convertToTspans, gd); + .call(svgTextUtils.convertToTspans, gd, convertOptions); return Plots.previousPromises(gd); } - function scootTitle(titleElIn) { - var titleGroup = d3.select(titleElIn.node().parentNode); + function scootTitle(titleEl) { + var titleGroup = d3.select(titleEl.node().parentNode); if(avoid && avoid.selection && avoid.side && txt) { titleGroup.attr('transform', null); diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index 55a4123cb70..a9bcdd77a16 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -17,7 +17,18 @@ function getSize(_selection, _dimension) { var FIND_TEX = /([^$]*)([$]+[^$]*[$]+)([^$]*)/; -exports.convertToTspans = function(_context, gd, _callback) { +/** + * Converts to SVG element. + * @param {*} _context Context + * @param {*} gd Graph DIV + * @param {*} options All props are needed to wrap. + * [axLength]?: number + * [orientation]?: 'v' | 'h' + * [wrap]?: boolean + * @param {Function} _callback Callback function. + * @returns Modified `_context`. + */ +exports.convertToTspans = function(_context, gd, options, _callback) { var str = _context.text(); // Until we get tex integrated more fully (so it can be used along with non-tex) @@ -50,7 +61,7 @@ exports.convertToTspans = function(_context, gd, _callback) { _context.text('') .style('white-space', 'pre'); - var hasLink = buildSVGText(_context.node(), str); + var hasLink = buildSVGText(_context.node(), str, options); if(hasLink) { // at least in Chrome, pointer-events does not seem @@ -428,17 +439,16 @@ function fromCodePoint(code) { ); } -/* - * buildSVGText: convert our pseudo-html into SVG tspan elements, and attach these - * to containerNode +/** + * Converts SVG `` elements from pseudo-html into, and attach these to `containerNode`. * * @param {svg text element} containerNode: the node to insert this text into * @param {string} str: the pseudo-html string to convert to svg - * + * @param {{ axLength: number, axOrientation: 'v' | 'h', wrap?: boolean }} options * @returns {bool}: does the result contain any links? We need to handle the text element * somewhat differently if it does, so just keep track of this when it happens. */ -function buildSVGText(containerNode, str) { +function buildSVGText(containerNode, str, options) { /* * Normalize behavior between IE and others wrt newlines and whitespace:pre * this combination makes IE barf https://github.com/plotly/plotly.js/issues/746 @@ -446,6 +456,7 @@ function buildSVGText(containerNode, str) { * I feel like at some point we turned these into
but currently we don't so * I'm just going to cement what we do now in Chrome and FF */ + str = options && options.axOrientation === 'v' ? 'One very long string that is soo long, that
I dont get it!' : str; str = str.replace(NEWLINES, ' '); var hasLink = false; @@ -530,7 +541,11 @@ function buildSVGText(containerNode, str) { } function addTextNode(node, text) { - node.appendChild(document.createTextNode(text)); + return node.appendChild(document.createTextNode(text)); + } + + function removeTextNode(node, child) { + node.removeChild(child); } function exitNode(type) { @@ -550,16 +565,19 @@ function buildSVGText(containerNode, str) { currentNode = nodeStack[nodeStack.length - 1].node; } - var hasLines = BR_TAG.test(str); + var hasBrLines = BR_TAG.test(str); - if(hasLines) newLine(); + if(hasBrLines) newLine(); else { currentNode = containerNode; nodeStack = [{node: containerNode}]; } var parts = str.split(SPLIT_TAGS); - for(var i = 0; i < parts.length; i++) { + // eslint-disable-next-line no-console + // options && options.axOrientation === 'v' && console.log(parts); + var i = 0; + for(i; i < parts.length; i++) { var parti = parts[i]; var match = parti.match(ONE_TAG); var tagType = match && match[2].toLowerCase(); @@ -568,7 +586,33 @@ function buildSVGText(containerNode, str) { if(tagType === 'br') { newLine(); } else if(tagStyle === undefined) { - addTextNode(currentNode, convertEntities(parti)); + // addTextNode(currentNode, convertEntities(parti)); + + if(options && options.axOrientation === 'v') { + if(options.wrap) { + var wordId = 0; + var wordsArray = parti.split(' '); + for(wordId; wordId < wordsArray.length; wordId++) { + var word = wordsArray[wordId]; + var preSpace = wordId === 0 ? '' : ' '; + var child = addTextNode(currentNode, convertEntities(preSpace + word)); + if(currentNode.getBBox().width > options.axLength) { + removeTextNode(currentNode, child); + newLine(); + addTextNode(currentNode, convertEntities(word)); + } + // eslint-disable-next-line no-console + // console.log(currentNode.getBBox().width); + } + + // eslint-disable-next-line no-console + // options && options.axOrientation === 'v' && console.log(currentNode.getBoundingClientRect().height); + // eslint-disable-next-line no-console + // options && options.axOrientation === 'v' && console.log(currentNode.getBBox().width); + } else { + addTextNode(currentNode, convertEntities(parti)); + } + } } else { // tag - open or close if(match[1]) { diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 44ebc870209..842121904ea 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -3464,6 +3464,8 @@ function drawTitle(gd, ax) { var axId = ax._id; var axLetter = axId.charAt(0); var fontSize = ax.title.font.size; + // var wrap = ax.title.wrap; // TODO: Update documentation and the TypeScript types. + var titleStandoff; if(ax.title.hasOwnProperty('standoff')) { @@ -3536,7 +3538,12 @@ function drawTitle(gd, ax) { placeholder: fullLayout._dfltTitle[axLetter], avoid: avoid, transform: transform, - attributes: {x: x, y: y, 'text-anchor': 'middle'} + attributes: {x: x, y: y, 'text-anchor': 'middle'}, + isAxis: true, + // wrap: wrap + // HEY! This is for testing only! + wrap: 'breakword', + // TODO: options: 'breakword' | 'breakchar' | undefined (default) }); } diff --git a/src/traces/table/plot.js b/src/traces/table/plot.js index 4ef177226f3..10fc0f46497 100644 --- a/src/traces/table/plot.js +++ b/src/traces/table/plot.js @@ -561,7 +561,7 @@ function populateCellText(cellText, tableControlView, allColumnBlock, gd) { var renderCallback = d.wrappingNeeded ? wrapTextMaker : updateYPositionMaker; if(d.needsConvertToTspans) { - svgUtil.convertToTspans(selection, gd, renderCallback(allColumnBlock, element, tableControlView, gd, d)); + svgUtil.convertToTspans(selection, gd, null, renderCallback(allColumnBlock, element, tableControlView, gd, d)); } else { d3.select(element.parentNode) // basic cell adjustment - compliance with `cellPad` From 66f50aa33ca5cffa775cc93ff99a9bf9063920da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Podle=C5=9Bny?= <17722512+mfazer@users.noreply.github.com> Date: Tue, 1 Jun 2021 13:50:25 +0200 Subject: [PATCH 2/3] Minor fix: Vars renamed --- src/components/titles/index.js | 2 +- src/lib/svg_text_utils.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/titles/index.js b/src/components/titles/index.js index 21408ca8255..3c45205fa84 100644 --- a/src/components/titles/index.js +++ b/src/components/titles/index.js @@ -122,7 +122,7 @@ function draw(gd, titleClass, options) { function drawTitle(titleEl) { var transformVal; - var convertOptions = null; + var convertOptions; if(transform) { transformVal = ''; diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index a9bcdd77a16..668c7a8d788 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -22,7 +22,7 @@ var FIND_TEX = /([^$]*)([$]+[^$]*[$]+)([^$]*)/; * @param {*} _context Context * @param {*} gd Graph DIV * @param {*} options All props are needed to wrap. - * [axLength]?: number + * [axisLength]?: number * [orientation]?: 'v' | 'h' * [wrap]?: boolean * @param {Function} _callback Callback function. @@ -444,7 +444,7 @@ function fromCodePoint(code) { * * @param {svg text element} containerNode: the node to insert this text into * @param {string} str: the pseudo-html string to convert to svg - * @param {{ axLength: number, axOrientation: 'v' | 'h', wrap?: boolean }} options + * @param {{ axisLength: number, axisOrientation: 'v' | 'h', wrap?: boolean }} options * @returns {bool}: does the result contain any links? We need to handle the text element * somewhat differently if it does, so just keep track of this when it happens. */ @@ -456,7 +456,7 @@ function buildSVGText(containerNode, str, options) { * I feel like at some point we turned these into
but currently we don't so * I'm just going to cement what we do now in Chrome and FF */ - str = options && options.axOrientation === 'v' ? 'One very long string that is soo long, that
I dont get it!' : str; + str = options && options.axisOrientation === 'v' ? 'One very long string that is soo long, that
I dont get it!' : str; str = str.replace(NEWLINES, ' '); var hasLink = false; @@ -575,7 +575,7 @@ function buildSVGText(containerNode, str, options) { var parts = str.split(SPLIT_TAGS); // eslint-disable-next-line no-console - // options && options.axOrientation === 'v' && console.log(parts); + // options && options.axisOrientation === 'v' && console.log(parts); var i = 0; for(i; i < parts.length; i++) { var parti = parts[i]; @@ -588,7 +588,7 @@ function buildSVGText(containerNode, str, options) { } else if(tagStyle === undefined) { // addTextNode(currentNode, convertEntities(parti)); - if(options && options.axOrientation === 'v') { + if(options && options.axisOrientation === 'v') { if(options.wrap) { var wordId = 0; var wordsArray = parti.split(' '); @@ -596,7 +596,7 @@ function buildSVGText(containerNode, str, options) { var word = wordsArray[wordId]; var preSpace = wordId === 0 ? '' : ' '; var child = addTextNode(currentNode, convertEntities(preSpace + word)); - if(currentNode.getBBox().width > options.axLength) { + if(currentNode.getBBox().width > options.axisLength) { removeTextNode(currentNode, child); newLine(); addTextNode(currentNode, convertEntities(word)); @@ -606,9 +606,9 @@ function buildSVGText(containerNode, str, options) { } // eslint-disable-next-line no-console - // options && options.axOrientation === 'v' && console.log(currentNode.getBoundingClientRect().height); + // options && options.axisOrientation === 'v' && console.log(currentNode.getBoundingClientRect().height); // eslint-disable-next-line no-console - // options && options.axOrientation === 'v' && console.log(currentNode.getBBox().width); + // options && options.axisOrientation === 'v' && console.log(currentNode.getBBox().width); } else { addTextNode(currentNode, convertEntities(parti)); } From e00d52831b520757a273ab82abe513db99fd4db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Podle=C5=9Bny?= <17722512+mfazer@users.noreply.github.com> Date: Tue, 1 Jun 2021 15:32:06 +0200 Subject: [PATCH 3/3] Minor vars renaming / Comments update / code cleaning --- src/components/titles/index.js | 10 +++--- src/lib/svg_text_utils.js | 56 +++++++++++++--------------------- src/plots/cartesian/axes.js | 7 ++--- 3 files changed, 28 insertions(+), 45 deletions(-) diff --git a/src/components/titles/index.js b/src/components/titles/index.js index 3c45205fa84..39ed094c664 100644 --- a/src/components/titles/index.js +++ b/src/components/titles/index.js @@ -56,7 +56,7 @@ function draw(gd, titleClass, options) { var attributes = options.attributes; var transform = options.transform; var group = options.containerGroup; - var isAxis = options.isAxis; // Prepare documentation for this + var isAxis = options.isAxis; var wrap = options.wrap; var fullLayout = gd._fullLayout; @@ -137,13 +137,13 @@ function draw(gd, titleClass, options) { } if(isAxis && wrap) { - var axName = options.propContainer._name; - var axOut = gd._fullLayout[axName]; + var axisName = options.propContainer._name; + var axis = gd._fullLayout[axisName]; convertOptions = { wrap: wrap, - axisLength: axOut._length, - axisOrientation: axOut._id.substr(0, 1) === 'y' ? 'v' : 'h' + axisLength: axis._length, + axisOrientation: axis._id.substr(0, 1) === 'y' ? 'v' : 'h' }; } diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index 668c7a8d788..2c82c97fd3b 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -19,13 +19,10 @@ var FIND_TEX = /([^$]*)([$]+[^$]*[$]+)([^$]*)/; /** * Converts to SVG element. - * @param {*} _context Context - * @param {*} gd Graph DIV - * @param {*} options All props are needed to wrap. - * [axisLength]?: number - * [orientation]?: 'v' | 'h' - * [wrap]?: boolean - * @param {Function} _callback Callback function. + * @param {*} _context + * @param {*} gd `graphDiv`. + * @param {{ axisLength: number, axisOrientation: 'v' | 'h', wrap?: boolean }} options + * @param {Function} _callback * @returns Modified `_context`. */ exports.convertToTspans = function(_context, gd, options, _callback) { @@ -440,7 +437,7 @@ function fromCodePoint(code) { } /** - * Converts SVG `` elements from pseudo-html into, and attach these to `containerNode`. + * Converts our pseudo-html SVG `` into elements, and attach these to `containerNode`. * * @param {svg text element} containerNode: the node to insert this text into * @param {string} str: the pseudo-html string to convert to svg @@ -456,7 +453,6 @@ function buildSVGText(containerNode, str, options) { * I feel like at some point we turned these into
but currently we don't so * I'm just going to cement what we do now in Chrome and FF */ - str = options && options.axisOrientation === 'v' ? 'One very long string that is soo long, that
I dont get it!' : str; str = str.replace(NEWLINES, ' '); var hasLink = false; @@ -574,8 +570,7 @@ function buildSVGText(containerNode, str, options) { } var parts = str.split(SPLIT_TAGS); - // eslint-disable-next-line no-console - // options && options.axisOrientation === 'v' && console.log(parts); + var i = 0; for(i; i < parts.length; i++) { var parti = parts[i]; @@ -586,31 +581,22 @@ function buildSVGText(containerNode, str, options) { if(tagType === 'br') { newLine(); } else if(tagStyle === undefined) { - // addTextNode(currentNode, convertEntities(parti)); - - if(options && options.axisOrientation === 'v') { - if(options.wrap) { - var wordId = 0; - var wordsArray = parti.split(' '); - for(wordId; wordId < wordsArray.length; wordId++) { - var word = wordsArray[wordId]; - var preSpace = wordId === 0 ? '' : ' '; - var child = addTextNode(currentNode, convertEntities(preSpace + word)); - if(currentNode.getBBox().width > options.axisLength) { - removeTextNode(currentNode, child); - newLine(); - addTextNode(currentNode, convertEntities(word)); - } - // eslint-disable-next-line no-console - // console.log(currentNode.getBBox().width); - } + if(!(options && options.wrap)) return void(addTextNode(currentNode, convertEntities(parti))); - // eslint-disable-next-line no-console - // options && options.axisOrientation === 'v' && console.log(currentNode.getBoundingClientRect().height); - // eslint-disable-next-line no-console - // options && options.axisOrientation === 'v' && console.log(currentNode.getBBox().width); - } else { - addTextNode(currentNode, convertEntities(parti)); + var wordId = 0; + var wordsArray = parti.split(' '); + + newLine(); + + for(wordId; wordId < wordsArray.length; wordId++) { + var word = wordsArray[wordId]; + var preSpace = wordId === 0 ? '' : ' '; + var child = addTextNode(currentNode, convertEntities(preSpace + word)); + + if(currentNode.getBBox().width > options.axisLength) { + removeTextNode(currentNode, child); + newLine(); + addTextNode(currentNode, convertEntities(word)); } } } else { diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 842121904ea..7b16ab6c298 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -3464,7 +3464,7 @@ function drawTitle(gd, ax) { var axId = ax._id; var axLetter = axId.charAt(0); var fontSize = ax.title.font.size; - // var wrap = ax.title.wrap; // TODO: Update documentation and the TypeScript types. + var wrap = ax.title.wrap; // TODO: Update our API documentation and the TypeScript types!. var titleStandoff; @@ -3539,11 +3539,8 @@ function drawTitle(gd, ax) { avoid: avoid, transform: transform, attributes: {x: x, y: y, 'text-anchor': 'middle'}, + wrap: wrap, isAxis: true, - // wrap: wrap - // HEY! This is for testing only! - wrap: 'breakword', - // TODO: options: 'breakword' | 'breakchar' | undefined (default) }); }