diff --git a/packages/victory-core/src/victory-util/textsize.ts b/packages/victory-core/src/victory-util/textsize.ts index d7d2e80a3..01a40db03 100644 --- a/packages/victory-core/src/victory-util/textsize.ts +++ b/packages/victory-core/src/victory-util/textsize.ts @@ -1,6 +1,6 @@ // http://www.pearsonified.com/2012/01/characters-per-line.php /* eslint-disable no-magic-numbers */ -import { assign, defaults } from "lodash"; +import { assign, defaults, memoize } from "lodash"; // Based on measuring specific character widths // as in the following example https://bl.ocks.org/tophtucker/62f93a4658387bb61e4510c37e2e97cf @@ -243,6 +243,77 @@ const _approximateTextHeightInternal = (text: string | string[], style) => { }, 0); }; +const _approximateDimensionsInternal = ( + text: string | string[], + style?: TextSizeStyleInterface, +) => { + const angle = Array.isArray(style) + ? style[0] && style[0].angle + : style && style.angle; + const height = _approximateTextHeightInternal(text, style); + const width = _approximateTextWidthInternal(text, style); + const widthWithRotate = angle + ? _getSizeWithRotate(width, height, angle) + : width; + const heightWithRotate = angle + ? _getSizeWithRotate(height, width, angle) + : height; + return { + width: widthWithRotate, + height: heightWithRotate * coefficients.heightOverlapCoef, + }; +}; + +const _getMeasurementContainer = memoize(() => { + const element = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + element.setAttribute("xlink", "http://www.w3.org/1999/xlink"); + + const containerElement = document.createElementNS( + "http://www.w3.org/2000/svg", + "text", + ); + element.appendChild(containerElement); + + element.style.position = "fixed"; + element.style.top = "-9999px"; + element.style.left = "-9999px"; + + document.body.appendChild(element); + + return containerElement; +}); + +const _measureDimensionsInternal = ( + text: string | string[], + style?: TextSizeStyleInterface, +) => { + const containerElement = _getMeasurementContainer(); + + const lines = _splitToLines(text); + for (const [i, line] of lines.entries()) { + const textElement = document.createElementNS( + "http://www.w3.org/2000/svg", + "tspan", + ); + const params = _prepareParams(style, i); + textElement.style.fontFamily = params.fontFamily; + textElement.style.transform = `rotate(${params.angle})`; + textElement.style.fontSize = `${params.fontSize}px`; + textElement.style.lineHeight = params.lineHeight; + textElement.style.fontFamily = params.fontFamily; + textElement.style.letterSpacing = params.letterSpacing; + textElement.textContent = line; + + containerElement.appendChild(textElement); + } + + const { width, height } = containerElement.getBoundingClientRect(); + + containerElement.innerHTML = ""; + + return { width, height }; +}; + export interface TextSizeStyleInterface { angle?: number; characterConstant?: string; @@ -255,21 +326,18 @@ export interface TextSizeStyleInterface { // Stubbable implementation. export const _approximateTextSizeInternal = { impl: (text: string | string[], style?: TextSizeStyleInterface) => { - const angle = Array.isArray(style) - ? style[0] && style[0].angle - : style && style.angle; - const height = _approximateTextHeightInternal(text, style); - const width = _approximateTextWidthInternal(text, style); - const widthWithRotate = angle - ? _getSizeWithRotate(width, height, angle) - : width; - const heightWithRotate = angle - ? _getSizeWithRotate(height, width, angle) - : height; - return { - width: widthWithRotate, - height: heightWithRotate * coefficients.heightOverlapCoef, - }; + // Attempt to first measure the element in DOM. If there is no DOM, fallback + // to the less accurate approximation algorithm. + const isClient = + typeof window !== "undefined" && + typeof window.document !== "undefined" && + typeof window.document.createElement !== "undefined"; + + if (!isClient) { + return _approximateDimensionsInternal(text, style); + } + + return _measureDimensionsInternal(text, style); }, }; diff --git a/stories/victory-label.stories.js b/stories/victory-label.stories.js index 322c92b9f..66b094861 100644 --- a/stories/victory-label.stories.js +++ b/stories/victory-label.stories.js @@ -415,6 +415,30 @@ export const LineHeight = () => { /> } /> + + } + /> + + } + /> ); };