From b58c17944b44dcca758e45be7cc447ab9b6d538b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C4=B1l=20S=C3=B6nmez?= Date: Wed, 6 Oct 2021 16:42:21 +0300 Subject: [PATCH 1/2] NCI-Agency/anet#3885: Implement SvgText function component --- client/package.json | 1 + client/src/components/SvgText.js | 145 ++++++++++++++++++++ client/src/components/graphs/LikertScale.js | 6 +- client/yarn.lock | 18 +++ 4 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 client/src/components/SvgText.js diff --git a/client/package.json b/client/package.json index 364d91b4b8..dc9bdd3972 100644 --- a/client/package.json +++ b/client/package.json @@ -173,6 +173,7 @@ "react-tooltip": "4.2.21", "react-ultimate-pagination": "1.2.0", "react-use-dimensions": "1.2.1", + "reduce-css-calc": "2.1.8", "redux": "4.1.1", "redux-persist": "6.0.0", "resize-observer-polyfill": "1.5.1", diff --git a/client/src/components/SvgText.js b/client/src/components/SvgText.js new file mode 100644 index 0000000000..cbcd957e2e --- /dev/null +++ b/client/src/components/SvgText.js @@ -0,0 +1,145 @@ +import memoize from "lodash/memoize" +import PropTypes from "prop-types" +import React, { useEffect, useState } from "react" +import reduceCSSCalc from "reduce-css-calc" + +const MEASUREMENT_ELEMENT_ID = "svg-text-measurement-id" + +const SvgText = ({ + x, + y, + dx, + dy, + width, + lineHeight, + capHeight, + verticalAnchor, + textAnchor, + style, + children, + ...textProps +}) => { + const [wordsByLines, setWordsByLines] = useState([]) + useEffect(() => { + const calculatedWidths = calculateWordWidths(children, style) + setWordsByLines( + calculateWordByLines( + calculatedWidths.wordsWithComputedWidth, + calculatedWidths.spaceWidth, + width + ) + ) + }, [children, style, width]) + return ( + + {wordsByLines.map((line, index) => ( + + {line.words.join(" ")} + + ))} + + ) +} + +SvgText.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + dx: PropTypes.number, + dy: PropTypes.number, + width: PropTypes.number, + lineHeight: PropTypes.string, + capHeight: PropTypes.string, + verticalAnchor: PropTypes.oneOf(["start", "middle", "end"]), + textAnchor: PropTypes.oneOf(["start", "middle", "end", "inherit"]), + style: PropTypes.object, + children: PropTypes.node +} + +SvgText.defaultProps = { + x: 0, + y: 0, + dx: 0, + dy: 0, + lineHeight: "1em", + capHeight: "0.71em", // Magic number from d3 + textAnchor: "start", + verticalAnchor: "end" +} + +const getStartDy = (verticalAnchor, capHeight, wordsByLines, lineHeight) => { + switch (verticalAnchor) { + case "start": + return reduceCSSCalc(`calc(${capHeight})`) + case "middle": + return reduceCSSCalc( + `calc(${ + (wordsByLines.length - 1) / 2 + } * -${lineHeight} + (${capHeight} / 2))` + ) + default: + return reduceCSSCalc(`calc(${wordsByLines.length - 1} * -${lineHeight})`) + } +} + +const calculateWordByLines = ( + wordsWithComputedWidth, + spaceWidth, + lineWidth +) => { + return wordsWithComputedWidth.reduce((wordsByLines, { word, width }) => { + const currentLine = wordsByLines[wordsByLines.length - 1] + if ( + currentLine && + (lineWidth === null || currentLine.width + width < lineWidth) + ) { + currentLine.words.push(word) + currentLine.width += width + spaceWidth + } else { + const newLine = { words: [word], width } + wordsByLines.push(newLine) + } + return wordsByLines + }, []) +} + +const calculateWordWidths = (children, style) => { + const words = children ? children.toString().split(/\s+/) : [] + const wordsWithComputedWidth = words.map(word => ({ + word, + width: getStringWidthMemoized(word, style) + })) + const spaceWidth = getStringWidthMemoized("\u00A0", style) + return { wordsWithComputedWidth, spaceWidth } +} + +const getStringWidth = (str, style) => { + let textEl = document.getElementById(MEASUREMENT_ELEMENT_ID) + if (!textEl) { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") + textEl = document.createElementNS("http://www.w3.org/2000/svg", "text") + textEl.setAttribute("id", MEASUREMENT_ELEMENT_ID) + svg.appendChild(textEl) + document.body.appendChild(svg) + } + Object.assign(textEl.style, style) + textEl.textContent = str + return textEl.getComputedTextLength() +} + +const getStringWidthMemoized = memoize(getStringWidth) + +export default SvgText diff --git a/client/src/components/graphs/LikertScale.js b/client/src/components/graphs/LikertScale.js index 36fd33ca0c..6ce819c5b0 100644 --- a/client/src/components/graphs/LikertScale.js +++ b/client/src/components/graphs/LikertScale.js @@ -1,9 +1,9 @@ import { TRAFFIC_LIGHTS_LEVELS } from "components/graphs/utils" +import SvgText from "components/SvgText" import * as d3 from "d3" import _isEmpty from "lodash/isEmpty" import PropTypes from "prop-types" import React, { useCallback, useEffect, useRef } from "react" -import Text from "react-svg-text" import useDimensions from "react-use-dimensions" import utils from "utils" @@ -124,7 +124,7 @@ const LikertScale = ({ height={Math.max(0, containerHeight - 11)} width={Math.max(0, endX - startX)} /> - {level.label} - + ) })} diff --git a/client/yarn.lock b/client/yarn.lock index 782db23c08..d55e994538 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -7156,6 +7156,11 @@ css-shorthand-properties@^1.1.1: resolved "https://registry.yarnpkg.com/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz#1c808e63553c283f289f2dd56fcee8f3337bd935" integrity sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A== +css-unit-converter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" + integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== + css-value@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/css-value/-/css-value-0.0.1.tgz#5efd6c2eea5ea1fd6b6ac57ec0427b18452424ea" @@ -14627,6 +14632,11 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-value-parser@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" @@ -15709,6 +15719,14 @@ reduce-css-calc@1.3.0: math-expression-evaluator "^1.2.14" reduce-function-call "^1.0.1" +reduce-css-calc@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" + integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg== + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" + reduce-function-call@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.3.tgz#60350f7fb252c0a67eb10fd4694d16909971300f" From 47de935ac068715fe195d1f1fbaf412477819363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C4=B1l=20S=C3=B6nmez?= Date: Wed, 6 Oct 2021 16:43:29 +0300 Subject: [PATCH 2/2] NCI-Agency/anet#3885: Remove react-svg-text dependency --- client/package.json | 1 - client/yarn.lock | 37 +------------------------------------ 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/client/package.json b/client/package.json index dc9bdd3972..9e918f41db 100644 --- a/client/package.json +++ b/client/package.json @@ -168,7 +168,6 @@ "react-router-bootstrap": "0.25.0", "react-router-dom": "5.3.0", "react-scroll": "1.8.4", - "react-svg-text": "0.1.2", "react-toastify": "8.0.3", "react-tooltip": "4.2.21", "react-ultimate-pagination": "1.2.0", diff --git a/client/yarn.lock b/client/yarn.lock index d55e994538..4f89c4765b 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -5550,11 +5550,6 @@ bail@^1.0.0: resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== -balanced-match@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" - integrity sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg= - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -12993,11 +12988,6 @@ matches-selector@0.0.1: resolved "https://registry.yarnpkg.com/matches-selector/-/matches-selector-0.0.1.tgz#1df5262243ae341c1a0804dd302048267ac713bb" integrity sha1-HfUmIkOuNBwaCATdMCBIJnrHE7s= -math-expression-evaluator@^1.2.14: - version "1.3.8" - resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.3.8.tgz#320da3b2bc1512f4f50fc3020b2b1cd5c8e9d577" - integrity sha512-9FbRY3i6U+CbHgrdNbAUaisjWTozkm1ZfupYQJiZ87NtYHk2Zh9DvxMgp/fifxVhqTLpd5fCCLossUbpZxGeKw== - md5-hex@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-3.0.1.tgz#be3741b510591434b2784d79e556eefc2c9a8e5c" @@ -15494,15 +15484,6 @@ react-sizeme@^3.0.1: shallowequal "^1.1.0" throttle-debounce "^3.0.1" -react-svg-text@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/react-svg-text/-/react-svg-text-0.1.2.tgz#f6d290f64f1cefea8a4368b44a02d17f93f0d6d7" - integrity sha512-jomqqy6dCx37w1BQMd0h5sl5z/icQqfDH6QrXfI4+0FHaqzT1n2sXCVe7IyZ6kiZPHipngJSY6QiNIYXwzhw8w== - dependencies: - lodash.memoize "^4.1.2" - prop-types "^15.5.10" - reduce-css-calc "1.3.0" - react-syntax-highlighter@^13.5.3: version "13.5.3" resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-13.5.3.tgz#9712850f883a3e19eb858cf93fad7bb357eea9c6" @@ -15710,16 +15691,7 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -reduce-css-calc@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" - integrity sha1-dHyRTgSWFKTJz7umKYca0dKSdxY= - dependencies: - balanced-match "^0.4.2" - math-expression-evaluator "^1.2.14" - reduce-function-call "^1.0.1" - -reduce-css-calc@^2.1.8: +reduce-css-calc@2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg== @@ -15727,13 +15699,6 @@ reduce-css-calc@^2.1.8: css-unit-converter "^1.1.1" postcss-value-parser "^3.3.0" -reduce-function-call@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.3.tgz#60350f7fb252c0a67eb10fd4694d16909971300f" - integrity sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ== - dependencies: - balanced-match "^1.0.0" - redux-persist@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"