Skip to content

Commit

Permalink
Merge pull request #3886 from NCI-Agency/GH-3885-replace-react-svg-text
Browse files Browse the repository at this point in the history
Replace react svg text
  • Loading branch information
gjvoosten authored Oct 7, 2021
2 parents 274147a + 47de935 commit 24ad031
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 37 deletions.
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,11 @@
"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",
"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",
Expand Down
145 changes: 145 additions & 0 deletions client/src/components/SvgText.js
Original file line number Diff line number Diff line change
@@ -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 (
<text
x={x + dx}
y={y + dy}
textAnchor={textAnchor}
style={style}
{...textProps}
>
{wordsByLines.map((line, index) => (
<tspan
x={x + dx}
dy={
index === 0
? getStartDy(verticalAnchor, capHeight, wordsByLines, lineHeight)
: lineHeight
}
key={index}
>
{line.words.join(" ")}
</tspan>
))}
</text>
)
}

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
6 changes: 3 additions & 3 deletions client/src/components/graphs/LikertScale.js
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -124,7 +124,7 @@ const LikertScale = ({
height={Math.max(0, containerHeight - 11)}
width={Math.max(0, endX - startX)}
/>
<Text
<SvgText
fill={active ? "black" : "gray"}
fontWeight={active ? "bold" : "normal"}
x={startX + 2}
Expand All @@ -134,7 +134,7 @@ const LikertScale = ({
verticalAnchor="start"
>
{level.label}
</Text>
</SvgText>
</React.Fragment>
)
})}
Expand Down
49 changes: 16 additions & 33 deletions client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5709,11 +5709,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"
Expand Down Expand Up @@ -7380,6 +7375,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"
Expand Down Expand Up @@ -13405,11 +13405,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"
Expand Down Expand Up @@ -15114,6 +15109,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"
Expand Down Expand Up @@ -15984,15 +15984,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"
Expand Down Expand Up @@ -16200,21 +16191,13 @@ 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-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==
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:
balanced-match "^1.0.0"
css-unit-converter "^1.1.1"
postcss-value-parser "^3.3.0"

redux-persist@6.0.0:
version "6.0.0"
Expand Down

0 comments on commit 24ad031

Please sign in to comment.