diff --git a/package.json b/package.json index afaf4c75f..b2a146d17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@netdata/charts", - "version": "4.2.1", + "version": "4.3.0", "description": "Netdata frontend SDK and chart utilities", "main": "dist/index.js", "module": "dist/es6/index.js", diff --git a/src/chartLibraries/d3pie/index.js b/src/chartLibraries/d3pie/index.js index 740fda2bf..e379faaf1 100644 --- a/src/chartLibraries/d3pie/index.js +++ b/src/chartLibraries/d3pie/index.js @@ -2,7 +2,7 @@ import makeChartUI from "@/sdk/makeChartUI" import { unregister } from "@/helpers/makeListeners" import makeResizeObserver from "@/helpers/makeResizeObserver" import makeExecuteLatest from "@/helpers/makeExecuteLatest" -import shorten from "@/helpers/shorten" +import { shortForLength } from "@/helpers/shorten" import d3pie from "./library" import getInitialOptions from "./getInitialOptions" @@ -75,7 +75,7 @@ export default (sdk, chart) => { const values = dimensionIds .map(id => ({ - label: shorten(id, 30), + label: shortForLength(id, 30), value: chart.getDimensionValue(id, index), color: chart.selectDimensionColor(id), caption: id, diff --git a/src/components/bars/dimension.js b/src/components/bars/dimension.js index 5a7168a3a..d301ef3c1 100644 --- a/src/components/bars/dimension.js +++ b/src/components/bars/dimension.js @@ -66,7 +66,7 @@ const AnnotationsValue = ({ children: annotations, ...rest }) => ( ) -const Dimension = ({ id, strong, chars, rowFlavour, fullCols }) => { +const Dimension = ({ id, strong, rowFlavour, fullCols }) => { const visible = useVisibleDimensionId(id) const chart = useChart() @@ -82,7 +82,7 @@ const Dimension = ({ id, strong, chars, rowFlavour, fullCols }) => { > - + { key={id} id={id} strong={row === id} - chars={parseInt(width / (cols === "full" ? 15 : 8))} rowFlavour={rowFlavour} fullCols={cols === "full"} /> diff --git a/src/components/container.js b/src/components/container.js index 1363a7bee..46925611b 100644 --- a/src/components/container.js +++ b/src/components/container.js @@ -1,7 +1,7 @@ import styled from "styled-components" import { Flex } from "@netdata/netdata-ui" -const Container = styled(Flex).attrs(({ height, width, ...rest }) => ({ +const Container = styled(Flex).attrs(({ height = "100%", width = "100%", ...rest }) => ({ "data-testid": "chart", column: true, position: "relative", diff --git a/src/components/helpers/shortener.js b/src/components/helpers/shortener.js index 6f9950d55..a13715345 100644 --- a/src/components/helpers/shortener.js +++ b/src/components/helpers/shortener.js @@ -1,18 +1,35 @@ -import React, { useMemo } from "react" +import React, { useState, useEffect } from "react" import shorten from "@/helpers/shorten" import Tooltip from "@/components/tooltip" -const Shortener = ({ text, maxLength = 15, Component = "div", noTooltip, ...rest }) => { - const truncated = useMemo(() => (text ? shorten(text, maxLength) : null), [text, maxLength]) +const Shortener = ({ text, Component = "div", noTooltip, ...rest }) => { + const [shortenText, setShortenText] = useState("") - if (!noTooltip && truncated !== text) - return ( - - {truncated} - - ) + const [ref, setRef] = useState() - return {truncated} + useEffect(() => { + if (!ref) return + + const containerWidth = ref.offsetWidth + let round = 0 + + while (ref.scrollWidth > containerWidth) { + ref.textContent = shorten(ref.textContent, round) + round = round + 1 + } + + if (ref.textContent !== text) { + setShortenText(text) + } + }, [text, ref]) + + return ( + + + {text} + + + ) } export default Shortener diff --git a/src/components/hocs/withDeferredMount.js b/src/components/hocs/withDeferredMount.js index ad8e7b578..e418214ce 100644 --- a/src/components/hocs/withDeferredMount.js +++ b/src/components/hocs/withDeferredMount.js @@ -2,23 +2,25 @@ import React, { forwardRef } from "react" import { useChart, useImmediateListener } from "@/components/provider" export default Component => { - const DifferedMount = forwardRef(({ isVisible = true, ...rest }, ref) => { - const chart = useChart() + const DifferedMount = forwardRef( + ({ isVisible = true, height = "100%", width = "100%", ...rest }, ref) => { + const chart = useChart() - useImmediateListener(() => { - if (!isVisible) return - if (!!rest.uiName && rest.uiName !== "default") return + useImmediateListener(() => { + if (!isVisible) return + if (!!rest.uiName && rest.uiName !== "default") return - const id = window.requestAnimationFrame(chart.activate) + const id = window.requestAnimationFrame(chart.activate) - return () => { - window.cancelAnimationFrame(id) - chart.deactivate() - } - }, [isVisible, chart, rest.uiName]) + return () => { + window.cancelAnimationFrame(id) + chart.deactivate() + } + }, [isVisible, chart, rest.uiName]) - return - }) + return + } + ) return DifferedMount } diff --git a/src/components/hocs/withTile.js b/src/components/hocs/withTile.js index 88a8a7fcc..1a366c1cc 100644 --- a/src/components/hocs/withTile.js +++ b/src/components/hocs/withTile.js @@ -79,9 +79,9 @@ export const HeadWrapper = ({ children, uiName, ...rest }) => { return ( - + - + { {children} </Flex> - <Flex column width="24px" alignItems="center" padding={[4, 1]} gap={2}> + <Flex column width={5} alignItems="center" padding={[4, 0]} gap={2}> {firstDim === "selected" && ( <> <Flex @@ -145,13 +145,13 @@ export const ChartWrapper = styled(Flex).attrs(props => ({ }))`` export default Component => - ({ count, tile = true, ...rest }) => + ({ count, tile = true, height = "100%", width = "100%", ...rest }) => tile ? ( - <HeadWrapper count={count} uiName={rest.uiName}> + <HeadWrapper count={count} uiName={rest.uiName} height={height} width={width}> <Component {...rest} /> </HeadWrapper> ) : ( - <ChartHeadWrapper size={20}> + <ChartHeadWrapper size={20} height={height} width={width}> <Component {...rest} /> </ChartHeadWrapper> ) diff --git a/src/components/line/dimensions/name.js b/src/components/line/dimensions/name.js index 6ccf6b0ce..da74c92cb 100644 --- a/src/components/line/dimensions/name.js +++ b/src/components/line/dimensions/name.js @@ -4,10 +4,9 @@ import { useChart } from "@/components/provider" import Shortener from "@/components/helpers/shortener" export const Name = memo( - forwardRef(({ children, maxLength = 32, ...rest }, ref) => ( + forwardRef(({ children, ...rest }, ref) => ( <Shortener text={children} - maxLength={maxLength} Component={TextMicro} color="textDescription" whiteSpace="nowrap" diff --git a/src/components/line/legend/dimension.js b/src/components/line/legend/dimension.js index e0dcc8ce7..6bd7d7f13 100644 --- a/src/components/line/legend/dimension.js +++ b/src/components/line/legend/dimension.js @@ -106,7 +106,7 @@ const Dimension = forwardRef(({ id }, ref) => { content={visible ? <TooltipValue id={id} name={name} /> : null} > <Flex flex column overflow="hidden" data-testid="chartLegendDimension-details"> - <Name id={id} maxLength={32} noTooltip /> + <Name id={id} noTooltip /> <AnomalyProgressBar id={id} /> diff --git a/src/components/line/popover/dimension.js b/src/components/line/popover/dimension.js index 28e243d60..f616f6ace 100644 --- a/src/components/line/popover/dimension.js +++ b/src/components/line/popover/dimension.js @@ -64,7 +64,7 @@ const AnnotationsValue = ({ children: annotations, showFull, ...rest }) => ( </Flex> ) -const Dimension = ({ id, strong, chars, rowFlavour }) => { +const Dimension = ({ id, strong, rowFlavour }) => { const visible = useVisibleDimensionId(id) const chart = useChart() @@ -86,7 +86,6 @@ const Dimension = ({ id, strong, chars, rowFlavour }) => { flex id={id} strong={strong} - maxLength={chars} noTooltip color={strong ? "textFocus" : "text"} /> diff --git a/src/components/line/popover/dimensions.js b/src/components/line/popover/dimensions.js index 227233c53..32dd1ff28 100644 --- a/src/components/line/popover/dimensions.js +++ b/src/components/line/popover/dimensions.js @@ -144,13 +144,7 @@ const Dimensions = ({ uiName }) => { </TextMicro> </GridHeader> {ids.map(id => ( - <Dimension - key={id} - id={id} - strong={row === id} - chars={chartWidth < 600 ? 50 : 100} - rowFlavour={rowFlavour} - /> + <Dimension key={id} id={id} strong={row === id} rowFlavour={rowFlavour} /> ))} </Grid> <Flex flex={false} height={3}> diff --git a/src/components/tooltip/index.js b/src/components/tooltip/index.js index b7bc70d19..2e64c57d2 100644 --- a/src/components/tooltip/index.js +++ b/src/components/tooltip/index.js @@ -18,15 +18,19 @@ const DefaultContent = ({ children, ...rest }) => ( </Flex> ) -const Tooltip = forwardRef(({ content, Content = DefaultContent, ...rest }, ref) => ( - <BaseTooltip - ref={ref} - plain - content={<Content {...rest}>{content}</Content>} - {...rest} - dropProps={{ "data-toolbox": true }} - /> -)) +const Tooltip = forwardRef(({ content, Content = DefaultContent, ...rest }, ref) => + content ? ( + <BaseTooltip + ref={ref} + plain + content={<Content {...rest}>{content}</Content>} + {...rest} + dropProps={{ "data-toolbox": true }} + /> + ) : ( + rest.children + ) +) Tooltip.defaultProps = { align: "bottom", diff --git a/src/helpers/shorten/index.js b/src/helpers/shorten/index.js index 8345067a5..c062dcf2a 100644 --- a/src/helpers/shorten/index.js +++ b/src/helpers/shorten/index.js @@ -1,61 +1,54 @@ const removeMiddleVowels = word => { - if (/\d/.test(word)) return word + if (/\d/.test(word) || word.length < 4) return word + const middle = word.substring(1, word.length - 1) return [word.charAt(0), middle.replace(/([aeiou])/gi, ""), word.charAt(word.length - 1)].join("") } const removeDuplicateLetters = word => word.replace(/(\w)\1+/g, "$1") -const ellipsisInMiddle = (text, maxLength) => { - const partLength = Math.floor((maxLength - 3) / 2) +const ellipsisInMiddle = (text, length) => { + const partLength = Math.floor((text.length - length) / 2) const prefix = text.substring(0, partLength) const suffix = text.substring(text.length - partLength) return `${prefix}...${suffix}` } -const replaceIfNeeded = (text, func, maxLength) => { - if (text.length <= maxLength) return text - return text.replace(/(\w.+?|\d.+)([\s-_.@]+?)/g, (_, word, sep) => { - word = func(word, maxLength) +const replaceIfNeeded = (text, func, ...args) => { + if (!text) return "" - return `${word}${sep}` + return text.replace(/([\w\d].+?)([\s-_@])([\w\d].+)+?/, (_, word, sep, word2) => { + return `${func(word, ...args)}${sep}${replaceIfNeeded(word2, func, ...args)}` }) } -const shortenSingleString = (string, maxLength) => { - string = removeDuplicateLetters(removeMiddleVowels(string)) - - if (string.length <= maxLength) return string - - return ellipsisInMiddle(string, maxLength) +const shorten = (string, round = 0) => { + if (!string || typeof string !== "string") return string + + switch (round) { + case 0: + return string.trim() + case 1: + return replaceIfNeeded(string, removeDuplicateLetters) + case 2: + return replaceIfNeeded(string, removeMiddleVowels) + default: + return ellipsisInMiddle(string, round) + } } -export default (string, maxLength = 60) => { - if (!string) return string - if (string.length <= maxLength) return string - - const match = string.trim().match(/(.+[\s-_.@])(.+)$/) - - if (!match) return shortenSingleString(string, maxLength) +export const shortForLength = (string, maxLength = 30) => { + if (!string || typeof string !== "string") return string - let [, text, lastText] = match + let round = 0 - const hasSeparators = text.match(/[\s-_.@]/) - - if (hasSeparators) { - text = replaceIfNeeded(text, removeMiddleVowels, maxLength - lastText.length) - text = replaceIfNeeded(text, removeDuplicateLetters, maxLength - lastText.length) - } else { - text = removeDuplicateLetters(removeMiddleVowels(lastText)) + while (string.length > maxLength) { + string = shorten(string, round) + round = round + 1 } - if ((text + lastText).length <= maxLength) return text + lastText - - lastText = removeDuplicateLetters(removeMiddleVowels(lastText)) - - if ((text + lastText).length <= maxLength) return text + lastText - - text = text + lastText - return ellipsisInMiddle(text, maxLength) + return string } + +export default shorten