From 87403f625ddbb0347dc03a6aba6e842c6164beaf Mon Sep 17 00:00:00 2001 From: Gabriel Restori Soares Date: Sat, 24 Oct 2020 16:20:21 +0200 Subject: [PATCH 1/9] Add textPath option on visx-text --- packages/visx-text/src/Text.tsx | 29 ++++-- packages/visx-text/test/Text.test.tsx | 138 +++++++++++++++----------- 2 files changed, 103 insertions(+), 64 deletions(-) diff --git a/packages/visx-text/src/Text.tsx b/packages/visx-text/src/Text.tsx index a3683a8c0..791f83151 100644 --- a/packages/visx-text/src/Text.tsx +++ b/packages/visx-text/src/Text.tsx @@ -1,6 +1,7 @@ import React from 'react'; import reduceCSSCalc from 'reduce-css-calc'; import getStringWidth from './util/getStringWidth'; +import uniqueId from 'lodash/uniqueId'; const SVG_STYLE = { overflow: 'visible' }; @@ -67,12 +68,15 @@ type OwnProps = { width?: number; /** String (or number coercible to one) to be styled and positioned. */ children?: string | number; + /** Path for the text to follow along. */ + textPath?: string; }; export type TextProps = OwnProps & Omit; type TextState = { wordsByLines: WordsWithWidth[]; + textPathId: string; }; class Text extends React.Component { @@ -90,6 +94,7 @@ class Text extends React.Component { state: TextState = { wordsByLines: [], + textPathId: '' }; private wordsWithWidth: WordWithWidth[] = []; @@ -98,6 +103,7 @@ class Text extends React.Component { componentDidMount() { this.updateWordsByLines(this.props, true); + this.updateTextPathId(this.props); } componentDidUpdate(prevProps: TextProps, prevState: TextState) { @@ -111,6 +117,12 @@ class Text extends React.Component { this.updateWordsByLines(this.props, needCalculate); } + updateTextPathId(props: TextProps) { + if(props.textPath) { + this.setState({ textPathId: uniqueId('text-path-') }) + } + } + updateWordsByLines(props: TextProps, needCalculate: boolean = false) { // Only perform calculations if using features that require them (multiline, scaleToFit) if (props.width || props.scaleToFit) { @@ -178,10 +190,11 @@ class Text extends React.Component { capHeight, innerRef, width, + textPath, ...textProps } = this.props; - const { wordsByLines } = this.state; + const { wordsByLines, textPathId } = this.state; const { x, y } = textProps; // Cannot render if x or y is invalid @@ -214,15 +227,19 @@ class Text extends React.Component { } const transform = transforms.length > 0 ? transforms.join(' ') : undefined; + const text = wordsByLines.map((line, index) => ( + + {line.words.join(' ')} + + )); return ( + {textPath && } - {wordsByLines.map((line, index) => ( - - {line.words.join(' ')} - - ))} + {textPath ? ( + {text} + ) : text} ); diff --git a/packages/visx-text/test/Text.test.tsx b/packages/visx-text/test/Text.test.tsx index 6eb9d1e07..18117ca0d 100644 --- a/packages/visx-text/test/Text.test.tsx +++ b/packages/visx-text/test/Text.test.tsx @@ -1,165 +1,187 @@ -import React from 'react'; -import { shallow, mount } from 'enzyme'; -import { Text, getStringWidth } from '../src'; -import { addMock, removeMock } from './svgMock'; +import React from "react"; +import { shallow, mount } from "enzyme"; +import { Text, getStringWidth } from "../src"; +import { addMock, removeMock } from "./svgMock"; -describe('getStringWidth()', () => { - it('should be defined', () => { +describe("getStringWidth()", () => { + it("should be defined", () => { expect(getStringWidth).toBeDefined(); }); }); -describe('', () => { +describe("", () => { beforeEach(addMock); afterEach(removeMock); - it('should be defined', () => { + it("should be defined", () => { expect(Text).toBeDefined(); }); - it('should not throw', () => { + it("should not throw", () => { expect(() => shallow()).not.toThrow(); expect(() => shallow(Hi)).not.toThrow(); }); - it('Does not wrap long text if enough width', () => { + it("Does not wrap long text if enough width", () => { const wrapper = shallow( - + This is really long text - , + ); expect(wrapper.instance().state.wordsByLines).toHaveLength(1); }); - it('Wraps text if not enough width', () => { + it("Wraps text if not enough width", () => { const wrapper = shallow( - + This is really long text - , + ); expect(wrapper.instance().state.wordsByLines).toHaveLength(2); }); - it('Does not wrap text if there is enough width', () => { + it("Does not wrap text if there is enough width", () => { const wrapper = shallow( - + This is really long text - , + ); expect(wrapper.instance().state.wordsByLines).toHaveLength(1); }); - it('Does not perform word length calculation if width or scaleToFit props not set', () => { + it("Does not perform word length calculation if width or scaleToFit props not set", () => { const wrapper = shallow(This is really long text); expect(wrapper.instance().state.wordsByLines).toHaveLength(1); expect(wrapper.instance().state.wordsByLines[0].width).toBeUndefined(); }); - it('Render 0 success when specify the width', () => { + it("Render 0 success when specify the width", () => { const wrapper = mount( 0 - , + ); - console.log('wrapper', wrapper.text()); - expect(wrapper.text()).toContain('0'); + console.log("wrapper", wrapper.text()); + expect(wrapper.text()).toContain("0"); }); - it('Render 0 success when not specify the width', () => { + it("Render 0 success when not specify the width", () => { const wrapper = mount( 0 - , + ); - expect(wrapper.text()).toContain('0'); + expect(wrapper.text()).toContain("0"); }); - it('Render text when x or y is a percentage', () => { + it("Render text when x or y is a percentage", () => { const wrapper = mount( anything - , + ); - expect(wrapper.text()).toContain('anything'); + expect(wrapper.text()).toContain("anything"); }); it("Don't Render text when x or y is NaN", () => { const wrapperNan = mount( anything - , + ); - expect(wrapperNan.text()).not.toContain('anything'); + expect(wrapperNan.text()).not.toContain("anything"); }); - it('Render text when children 0 is a number', () => { + it("Render text when children 0 is a number", () => { const wrapper = mount( {0} - , + ); - expect(wrapper.text()).toContain('0'); + expect(wrapper.text()).toContain("0"); }); - it('Recalculates text when children are updated', () => { + it("Recalculates text when children are updated", () => { const wrapper = shallow( - + This is short text - , + ); expect(wrapper.instance().state.wordsByLines).toHaveLength(1); - wrapper.setProps({ children: 'This is really long text' }); + wrapper.setProps({ children: "This is really long text" }); expect(wrapper.instance().state.wordsByLines).toHaveLength(2); }); - it('Applies transform if scaleToFit is set', () => { + it("Applies transform if scaleToFit is set", () => { const wrapper = shallow( - + This is really long text - , + ); - const text = wrapper.find('text').first(); - expect(text.prop('transform')).toBe('matrix(1.25, 0, 0, 1.25, 0, 0)'); + const text = wrapper.find("text").first(); + expect(text.prop("transform")).toBe("matrix(1.25, 0, 0, 1.25, 0, 0)"); }); - it('Applies transform if angle is given', () => { + it("Applies transform if angle is given", () => { const wrapper = shallow( - + This is really long text - , + ); - const text = wrapper.find('text').first(); - expect(text.prop('transform')).toBe('rotate(45, 0, 0)'); + const text = wrapper.find("text").first(); + expect(text.prop("transform")).toBe("rotate(45, 0, 0)"); }); - it('Offsets vertically if verticalAnchor is given', () => { + it("Offsets vertically if verticalAnchor is given", () => { const wrapper = shallow( - + This is really long text - , + ); const getVerticalOffset = (w: typeof wrapper) => w - .find('tspan') + .find("tspan") .first() - .prop('dy'); + .prop("dy"); - expect(getVerticalOffset(wrapper)).toBe('-1em'); - wrapper.setProps({ verticalAnchor: 'middle' }); - expect(getVerticalOffset(wrapper)).toBe('-0.145em'); - wrapper.setProps({ verticalAnchor: 'start' }); - expect(getVerticalOffset(wrapper)).toBe('0.71em'); + expect(getVerticalOffset(wrapper)).toBe("-1em"); + wrapper.setProps({ verticalAnchor: "middle" }); + expect(getVerticalOffset(wrapper)).toBe("-0.145em"); + wrapper.setProps({ verticalAnchor: "start" }); + expect(getVerticalOffset(wrapper)).toBe("0.71em"); + }); + it("Should render textPath when textPath is passed", () => { + const wrapper = mount(Text path test); + + const textPath = wrapper.find("textPath"); + const path = wrapper.find("path"); + + expect(textPath).toHaveLength(1); + expect(path).toHaveLength(1); + + expect(textPath.props().href).toEqual(`#${path.props().id}`); + expect(path.props().d).toEqual("M10 10"); + }); + + it("Should not render textPath when textPath is not passed", () => { + const wrapper = mount(Text path test); + + const textPath = wrapper.find("textPath"); + const path = wrapper.find("path"); + + expect(textPath).toHaveLength(0); + expect(path).toHaveLength(0); }); }); From e122d6396a83f9ab135d9b72decd26cb35966257 Mon Sep 17 00:00:00 2001 From: Gabriel Restori Soares Date: Sat, 24 Oct 2020 16:21:38 +0200 Subject: [PATCH 2/9] Remove useless constant on visx-text tests --- packages/visx-text/test/Text.test.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/visx-text/test/Text.test.tsx b/packages/visx-text/test/Text.test.tsx index 18117ca0d..120e85821 100644 --- a/packages/visx-text/test/Text.test.tsx +++ b/packages/visx-text/test/Text.test.tsx @@ -178,10 +178,7 @@ describe("", () => { it("Should not render textPath when textPath is not passed", () => { const wrapper = mount(Text path test); - const textPath = wrapper.find("textPath"); - const path = wrapper.find("path"); - - expect(textPath).toHaveLength(0); - expect(path).toHaveLength(0); + expect(wrapper.find("textPath")).toHaveLength(0); + expect(wrapper.find("path")).toHaveLength(0); }); }); From a22d863ca22f3f7cd26f318639ac921369257e2d Mon Sep 17 00:00:00 2001 From: Gabriel Restori Soares Date: Wed, 11 Nov 2020 15:57:16 +0100 Subject: [PATCH 3/9] Fix text test wuotes --- packages/visx-text/test/Text.test.tsx | 110 +++++++++++++------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/packages/visx-text/test/Text.test.tsx b/packages/visx-text/test/Text.test.tsx index 120e85821..44390269c 100644 --- a/packages/visx-text/test/Text.test.tsx +++ b/packages/visx-text/test/Text.test.tsx @@ -1,30 +1,30 @@ -import React from "react"; -import { shallow, mount } from "enzyme"; -import { Text, getStringWidth } from "../src"; -import { addMock, removeMock } from "./svgMock"; +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import { Text, getStringWidth } from '../src'; +import { addMock, removeMock } from './svgMock'; -describe("getStringWidth()", () => { - it("should be defined", () => { +describe('getStringWidth()', () => { + it('should be defined', () => { expect(getStringWidth).toBeDefined(); }); }); -describe("", () => { +describe('', () => { beforeEach(addMock); afterEach(removeMock); - it("should be defined", () => { + it('should be defined', () => { expect(Text).toBeDefined(); }); - it("should not throw", () => { + it('should not throw', () => { expect(() => shallow()).not.toThrow(); expect(() => shallow(Hi)).not.toThrow(); }); - it("Does not wrap long text if enough width", () => { + it('Does not wrap long text if enough width', () => { const wrapper = shallow( - + This is really long text ); @@ -32,9 +32,9 @@ describe("", () => { expect(wrapper.instance().state.wordsByLines).toHaveLength(1); }); - it("Wraps text if not enough width", () => { + it('Wraps text if not enough width', () => { const wrapper = shallow( - + This is really long text ); @@ -42,9 +42,9 @@ describe("", () => { expect(wrapper.instance().state.wordsByLines).toHaveLength(2); }); - it("Does not wrap text if there is enough width", () => { + it('Does not wrap text if there is enough width', () => { const wrapper = shallow( - + This is really long text ); @@ -52,42 +52,42 @@ describe("", () => { expect(wrapper.instance().state.wordsByLines).toHaveLength(1); }); - it("Does not perform word length calculation if width or scaleToFit props not set", () => { + it('Does not perform word length calculation if width or scaleToFit props not set', () => { const wrapper = shallow(This is really long text); expect(wrapper.instance().state.wordsByLines).toHaveLength(1); expect(wrapper.instance().state.wordsByLines[0].width).toBeUndefined(); }); - it("Render 0 success when specify the width", () => { + it('Render 0 success when specify the width', () => { const wrapper = mount( 0 ); - console.log("wrapper", wrapper.text()); - expect(wrapper.text()).toContain("0"); + console.log('wrapper', wrapper.text()); + expect(wrapper.text()).toContain('0'); }); - it("Render 0 success when not specify the width", () => { + it('Render 0 success when not specify the width', () => { const wrapper = mount( 0 ); - expect(wrapper.text()).toContain("0"); + expect(wrapper.text()).toContain('0'); }); - it("Render text when x or y is a percentage", () => { + it('Render text when x or y is a percentage', () => { const wrapper = mount( - + anything ); - expect(wrapper.text()).toContain("anything"); + expect(wrapper.text()).toContain('anything'); }); it("Don't Render text when x or y is NaN", () => { @@ -97,88 +97,88 @@ describe("", () => { ); - expect(wrapperNan.text()).not.toContain("anything"); + expect(wrapperNan.text()).not.toContain('anything'); }); - it("Render text when children 0 is a number", () => { + it('Render text when children 0 is a number', () => { const wrapper = mount( {0} ); - expect(wrapper.text()).toContain("0"); + expect(wrapper.text()).toContain('0'); }); - it("Recalculates text when children are updated", () => { + it('Recalculates text when children are updated', () => { const wrapper = shallow( - + This is short text ); expect(wrapper.instance().state.wordsByLines).toHaveLength(1); - wrapper.setProps({ children: "This is really long text" }); + wrapper.setProps({ children: 'This is really long text' }); expect(wrapper.instance().state.wordsByLines).toHaveLength(2); }); - it("Applies transform if scaleToFit is set", () => { + it('Applies transform if scaleToFit is set', () => { const wrapper = shallow( - + This is really long text ); - const text = wrapper.find("text").first(); - expect(text.prop("transform")).toBe("matrix(1.25, 0, 0, 1.25, 0, 0)"); + const text = wrapper.find('text').first(); + expect(text.prop('transform')).toBe('matrix(1.25, 0, 0, 1.25, 0, 0)'); }); - it("Applies transform if angle is given", () => { + it('Applies transform if angle is given', () => { const wrapper = shallow( - + This is really long text ); - const text = wrapper.find("text").first(); - expect(text.prop("transform")).toBe("rotate(45, 0, 0)"); + const text = wrapper.find('text').first(); + expect(text.prop('transform')).toBe('rotate(45, 0, 0)'); }); - it("Offsets vertically if verticalAnchor is given", () => { + it('Offsets vertically if verticalAnchor is given', () => { const wrapper = shallow( - + This is really long text ); const getVerticalOffset = (w: typeof wrapper) => w - .find("tspan") + .find('tspan') .first() - .prop("dy"); + .prop('dy'); - expect(getVerticalOffset(wrapper)).toBe("-1em"); - wrapper.setProps({ verticalAnchor: "middle" }); - expect(getVerticalOffset(wrapper)).toBe("-0.145em"); - wrapper.setProps({ verticalAnchor: "start" }); - expect(getVerticalOffset(wrapper)).toBe("0.71em"); + expect(getVerticalOffset(wrapper)).toBe('-1em'); + wrapper.setProps({ verticalAnchor: 'middle' }); + expect(getVerticalOffset(wrapper)).toBe('-0.145em'); + wrapper.setProps({ verticalAnchor: 'start' }); + expect(getVerticalOffset(wrapper)).toBe('0.71em'); }); - it("Should render textPath when textPath is passed", () => { - const wrapper = mount(Text path test); + it('Should render textPath when textPath is passed', () => { + const wrapper = mount(Text path test); - const textPath = wrapper.find("textPath"); - const path = wrapper.find("path"); + const textPath = wrapper.find('textPath'); + const path = wrapper.find('path'); expect(textPath).toHaveLength(1); expect(path).toHaveLength(1); expect(textPath.props().href).toEqual(`#${path.props().id}`); - expect(path.props().d).toEqual("M10 10"); + expect(path.props().d).toEqual('M10 10'); }); - it("Should not render textPath when textPath is not passed", () => { + it('Should not render textPath when textPath is not passed', () => { const wrapper = mount(Text path test); - expect(wrapper.find("textPath")).toHaveLength(0); - expect(wrapper.find("path")).toHaveLength(0); + expect(wrapper.find('textPath')).toHaveLength(0); + expect(wrapper.find('path')).toHaveLength(0); }); }); From 04a0f4099547547c159c780a7806ed0eeb9eb5a7 Mon Sep 17 00:00:00 2001 From: Gabriel Restori Soares Date: Wed, 11 Nov 2020 15:59:13 +0100 Subject: [PATCH 4/9] Fix text test trailing commas --- packages/visx-text/test/Text.test.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/visx-text/test/Text.test.tsx b/packages/visx-text/test/Text.test.tsx index 44390269c..befa1ad04 100644 --- a/packages/visx-text/test/Text.test.tsx +++ b/packages/visx-text/test/Text.test.tsx @@ -26,7 +26,7 @@ describe('', () => { const wrapper = shallow( This is really long text - + , ); expect(wrapper.instance().state.wordsByLines).toHaveLength(1); @@ -36,7 +36,7 @@ describe('', () => { const wrapper = shallow( This is really long text - + , ); expect(wrapper.instance().state.wordsByLines).toHaveLength(2); @@ -46,7 +46,7 @@ describe('', () => { const wrapper = shallow( This is really long text - + , ); expect(wrapper.instance().state.wordsByLines).toHaveLength(1); @@ -63,7 +63,7 @@ describe('', () => { const wrapper = mount( 0 - + , ); console.log('wrapper', wrapper.text()); @@ -74,7 +74,7 @@ describe('', () => { const wrapper = mount( 0 - + , ); expect(wrapper.text()).toContain('0'); @@ -84,7 +84,7 @@ describe('', () => { const wrapper = mount( anything - + , ); expect(wrapper.text()).toContain('anything'); @@ -94,7 +94,7 @@ describe('', () => { const wrapperNan = mount( anything - + , ); expect(wrapperNan.text()).not.toContain('anything'); @@ -104,7 +104,7 @@ describe('', () => { const wrapper = mount( {0} - + , ); expect(wrapper.text()).toContain('0'); @@ -114,7 +114,7 @@ describe('', () => { const wrapper = shallow( This is short text - + , ); expect(wrapper.instance().state.wordsByLines).toHaveLength(1); @@ -126,7 +126,7 @@ describe('', () => { const wrapper = shallow( This is really long text - + , ); const text = wrapper.find('text').first(); @@ -137,7 +137,7 @@ describe('', () => { const wrapper = shallow( This is really long text - + , ); const text = wrapper.find('text').first(); @@ -148,7 +148,7 @@ describe('', () => { const wrapper = shallow( This is really long text - + , ); const getVerticalOffset = (w: typeof wrapper) => w From aa188a9c83d4a6a714000f2e739f2f6969421c0c Mon Sep 17 00:00:00 2001 From: Gabriel Restori Soares Date: Wed, 11 Nov 2020 16:00:54 +0100 Subject: [PATCH 5/9] Fix text test wuotes --- packages/visx-text/test/Text.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/visx-text/test/Text.test.tsx b/packages/visx-text/test/Text.test.tsx index befa1ad04..4fec521bb 100644 --- a/packages/visx-text/test/Text.test.tsx +++ b/packages/visx-text/test/Text.test.tsx @@ -82,7 +82,7 @@ describe('', () => { it('Render text when x or y is a percentage', () => { const wrapper = mount( - + anything , ); From ee737e8a66b955f971f35c62627c3135c76a5a43 Mon Sep 17 00:00:00 2001 From: Gabriel Restori Soares Date: Tue, 24 Nov 2020 18:43:13 +0100 Subject: [PATCH 6/9] Move state initialization on Text to the constructor --- packages/visx-text/src/Text.tsx | 171 +++++++++++++++++++------------- 1 file changed, 102 insertions(+), 69 deletions(-) diff --git a/packages/visx-text/src/Text.tsx b/packages/visx-text/src/Text.tsx index 791f83151..7a39eb066 100644 --- a/packages/visx-text/src/Text.tsx +++ b/packages/visx-text/src/Text.tsx @@ -1,20 +1,20 @@ -import React from 'react'; -import reduceCSSCalc from 'reduce-css-calc'; -import getStringWidth from './util/getStringWidth'; -import uniqueId from 'lodash/uniqueId'; +import React from "react"; +import reduceCSSCalc from "reduce-css-calc"; +import getStringWidth from "./util/getStringWidth"; +import uniqueId from "lodash/uniqueId"; -const SVG_STYLE = { overflow: 'visible' }; +const SVG_STYLE = { overflow: "visible" }; function isNumber(val: unknown): val is number { - return typeof val === 'number'; + return typeof val === "number"; } function isValidXOrY(xOrY: string | number | undefined) { return ( // number that is not NaN or Infinity - (typeof xOrY === 'number' && Number.isFinite(xOrY)) || + (typeof xOrY === "number" && Number.isFinite(xOrY)) || // for percentage - typeof xOrY === 'string' + typeof xOrY === "string" ); } @@ -39,9 +39,9 @@ type OwnProps = { /** Rotational angle of the text. */ angle?: number; /** Horizontal text anchor. */ - textAnchor?: 'start' | 'middle' | 'end' | 'inherit'; + textAnchor?: "start" | "middle" | "end" | "inherit"; /** Vertical text anchor. */ - verticalAnchor?: 'start' | 'middle' | 'end'; + verticalAnchor?: "start" | "middle" | "end"; /** Styles to be applied to the text (and used in computation of its size). */ style?: React.CSSProperties; /** Ref passed to the Text SVG element. */ @@ -55,9 +55,9 @@ type OwnProps = { /** dy offset of the text. */ dy?: string | number; /** Desired "line height" of the text, implemented as y offsets. */ - lineHeight?: SVGTSpanProps['dy']; + lineHeight?: SVGTSpanProps["dy"]; /** Cap height of the text. */ - capHeight?: SVGTSpanProps['capHeight']; + capHeight?: SVGTSpanProps["capHeight"]; /** Font size of text. */ fontSize?: string | number; /** Font family of text. */ @@ -85,25 +85,27 @@ class Text extends React.Component { y: 0, dx: 0, dy: 0, - lineHeight: '1em', - capHeight: '0.71em', // Magic number from d3 + lineHeight: "1em", + capHeight: "0.71em", // Magic number from d3 scaleToFit: false, - textAnchor: 'start', - verticalAnchor: 'end', // default SVG behavior - }; - - state: TextState = { - wordsByLines: [], - textPathId: '' + textAnchor: "start", + verticalAnchor: "end", // default SVG behavior }; private wordsWithWidth: WordWithWidth[] = []; - private spaceWidth: number = 0; + constructor(props: TextProps) { + super(props); + + this.state = { + wordsByLines: [], + textPathId: props.textPath ? uniqueId("text-path-") : "", + }; + } + componentDidMount() { this.updateWordsByLines(this.props, true); - this.updateTextPathId(this.props); } componentDidUpdate(prevProps: TextProps, prevState: TextState) { @@ -113,33 +115,30 @@ class Text extends React.Component { } const needCalculate = - prevProps.children !== this.props.children || prevProps.style !== this.props.style; + prevProps.children !== this.props.children || + prevProps.style !== this.props.style; this.updateWordsByLines(this.props, needCalculate); } - updateTextPathId(props: TextProps) { - if(props.textPath) { - this.setState({ textPathId: uniqueId('text-path-') }) - } - } - updateWordsByLines(props: TextProps, needCalculate: boolean = false) { // Only perform calculations if using features that require them (multiline, scaleToFit) if (props.width || props.scaleToFit) { if (needCalculate) { const words: string[] = - props.children == null ? [] : props.children.toString().split(/(?:(?!\u00A0+)\s+)/); - this.wordsWithWidth = words.map(word => ({ + props.children == null + ? [] + : props.children.toString().split(/(?:(?!\u00A0+)\s+)/); + this.wordsWithWidth = words.map((word) => ({ word, width: getStringWidth(word, props.style) || 0, })); - this.spaceWidth = getStringWidth('\u00A0', props.style) || 0; + this.spaceWidth = getStringWidth("\u00A0", props.style) || 0; } const wordsByLines = this.calculateWordsByLines( this.wordsWithWidth, this.spaceWidth, - props.width, + props.width ); this.setState({ wordsByLines }); } else { @@ -149,33 +148,42 @@ class Text extends React.Component { updateWordsWithoutCalculate(props: TextProps) { const words = - props.children == null ? [] : props.children.toString().split(/(?:(?!\u00A0+)\s+)/); + props.children == null + ? [] + : props.children.toString().split(/(?:(?!\u00A0+)\s+)/); this.setState({ wordsByLines: [{ words }] }); } - calculateWordsByLines(wordsWithWidth: WordWithWidth[], spaceWidth: number, lineWidth?: number) { + calculateWordsByLines( + wordsWithWidth: WordWithWidth[], + spaceWidth: number, + lineWidth?: number + ) { const { scaleToFit } = this.props; - return wordsWithWidth.reduce((result: WordsWithWidth[], { word, width }) => { - const currentLine = result[result.length - 1]; - - if ( - currentLine && - (lineWidth == null || - scaleToFit || - (currentLine.width || 0) + width + spaceWidth < lineWidth) - ) { - // Word can be added to an existing line - currentLine.words.push(word); - currentLine.width = currentLine.width || 0; - currentLine.width += width + spaceWidth; - } else { - // Add first word to line or word is too long to scaleToFit on existing line - const newLine = { words: [word], width }; - result.push(newLine); - } + return wordsWithWidth.reduce( + (result: WordsWithWidth[], { word, width }) => { + const currentLine = result[result.length - 1]; - return result; - }, []); + if ( + currentLine && + (lineWidth == null || + scaleToFit || + (currentLine.width || 0) + width + spaceWidth < lineWidth) + ) { + // Word can be added to an existing line + currentLine.words.push(word); + currentLine.width = currentLine.width || 0; + currentLine.width += width + spaceWidth; + } else { + // Add first word to line or word is too long to scaleToFit on existing line + const newLine = { words: [word], width }; + result.push(newLine); + } + + return result; + }, + [] + ); } render() { @@ -199,22 +207,39 @@ class Text extends React.Component { // Cannot render if x or y is invalid if (!isValidXOrY(x) || !isValidXOrY(y)) { - return ; + return ( + + ); } let startDy: string | undefined; - if (verticalAnchor === 'start') { + if (verticalAnchor === "start") { startDy = reduceCSSCalc(`calc(${capHeight})`); - } else if (verticalAnchor === 'middle') { + } else if (verticalAnchor === "middle") { startDy = reduceCSSCalc( - `calc(${(wordsByLines.length - 1) / 2} * -${lineHeight} + (${capHeight} / 2))`, + `calc(${(wordsByLines.length - 1) / + 2} * -${lineHeight} + (${capHeight} / 2))` ); } else { - startDy = reduceCSSCalc(`calc(${wordsByLines.length - 1} * -${lineHeight})`); + startDy = reduceCSSCalc( + `calc(${wordsByLines.length - 1} * -${lineHeight})` + ); } const transforms = []; - if (isNumber(x) && isNumber(y) && isNumber(width) && scaleToFit && wordsByLines.length > 0) { + if ( + isNumber(x) && + isNumber(y) && + isNumber(width) && + scaleToFit && + wordsByLines.length > 0 + ) { const lineWidth = wordsByLines[0].width || 1; const sx = width / lineWidth; const sy = sx; @@ -226,20 +251,28 @@ class Text extends React.Component { transforms.push(`rotate(${angle}, ${x}, ${y})`); } - const transform = transforms.length > 0 ? transforms.join(' ') : undefined; + const transform = transforms.length > 0 ? transforms.join(" ") : undefined; const text = wordsByLines.map((line, index) => ( - - {line.words.join(' ')} + + {line.words.join(" ")} )); return ( - + {textPath && } - {textPath ? ( - {text} - ) : text} + {textPath ? ( + {text} + ) : ( + text + )} ); From e389e4f97484dccec17c46650971d5aad0b96c3a Mon Sep 17 00:00:00 2001 From: Gabriel Restori Soares Date: Tue, 24 Nov 2020 18:46:47 +0100 Subject: [PATCH 7/9] Fix formatting --- packages/visx-text/src/Text.tsx | 153 ++++++++++++-------------------- 1 file changed, 58 insertions(+), 95 deletions(-) diff --git a/packages/visx-text/src/Text.tsx b/packages/visx-text/src/Text.tsx index 7a39eb066..467e5adb6 100644 --- a/packages/visx-text/src/Text.tsx +++ b/packages/visx-text/src/Text.tsx @@ -1,20 +1,20 @@ -import React from "react"; -import reduceCSSCalc from "reduce-css-calc"; -import getStringWidth from "./util/getStringWidth"; -import uniqueId from "lodash/uniqueId"; +import React from 'react'; +import reduceCSSCalc from 'reduce-css-calc'; +import getStringWidth from './util/getStringWidth'; +import uniqueId from 'lodash/uniqueId'; -const SVG_STYLE = { overflow: "visible" }; +const SVG_STYLE = { overflow: 'visible' }; function isNumber(val: unknown): val is number { - return typeof val === "number"; + return typeof val === 'number'; } function isValidXOrY(xOrY: string | number | undefined) { return ( // number that is not NaN or Infinity - (typeof xOrY === "number" && Number.isFinite(xOrY)) || + (typeof xOrY === 'number' && Number.isFinite(xOrY)) || // for percentage - typeof xOrY === "string" + typeof xOrY === 'string' ); } @@ -39,9 +39,9 @@ type OwnProps = { /** Rotational angle of the text. */ angle?: number; /** Horizontal text anchor. */ - textAnchor?: "start" | "middle" | "end" | "inherit"; + textAnchor?: 'start' | 'middle' | 'end' | 'inherit'; /** Vertical text anchor. */ - verticalAnchor?: "start" | "middle" | "end"; + verticalAnchor?: 'start' | 'middle' | 'end'; /** Styles to be applied to the text (and used in computation of its size). */ style?: React.CSSProperties; /** Ref passed to the Text SVG element. */ @@ -55,9 +55,9 @@ type OwnProps = { /** dy offset of the text. */ dy?: string | number; /** Desired "line height" of the text, implemented as y offsets. */ - lineHeight?: SVGTSpanProps["dy"]; + lineHeight?: SVGTSpanProps['dy']; /** Cap height of the text. */ - capHeight?: SVGTSpanProps["capHeight"]; + capHeight?: SVGTSpanProps['capHeight']; /** Font size of text. */ fontSize?: string | number; /** Font family of text. */ @@ -85,11 +85,11 @@ class Text extends React.Component { y: 0, dx: 0, dy: 0, - lineHeight: "1em", - capHeight: "0.71em", // Magic number from d3 + lineHeight: '1em', + capHeight: '0.71em', // Magic number from d3 scaleToFit: false, - textAnchor: "start", - verticalAnchor: "end", // default SVG behavior + textAnchor: 'start', + verticalAnchor: 'end', // default SVG behavior }; private wordsWithWidth: WordWithWidth[] = []; @@ -100,8 +100,8 @@ class Text extends React.Component { this.state = { wordsByLines: [], - textPathId: props.textPath ? uniqueId("text-path-") : "", - }; + textPathId: props.textPath ? uniqueId('text-path-') : '', + } } componentDidMount() { @@ -115,8 +115,7 @@ class Text extends React.Component { } const needCalculate = - prevProps.children !== this.props.children || - prevProps.style !== this.props.style; + prevProps.children !== this.props.children || prevProps.style !== this.props.style; this.updateWordsByLines(this.props, needCalculate); } @@ -125,20 +124,18 @@ class Text extends React.Component { if (props.width || props.scaleToFit) { if (needCalculate) { const words: string[] = - props.children == null - ? [] - : props.children.toString().split(/(?:(?!\u00A0+)\s+)/); - this.wordsWithWidth = words.map((word) => ({ + props.children == null ? [] : props.children.toString().split(/(?:(?!\u00A0+)\s+)/); + this.wordsWithWidth = words.map(word => ({ word, width: getStringWidth(word, props.style) || 0, })); - this.spaceWidth = getStringWidth("\u00A0", props.style) || 0; + this.spaceWidth = getStringWidth('\u00A0', props.style) || 0; } const wordsByLines = this.calculateWordsByLines( this.wordsWithWidth, this.spaceWidth, - props.width + props.width, ); this.setState({ wordsByLines }); } else { @@ -148,42 +145,33 @@ class Text extends React.Component { updateWordsWithoutCalculate(props: TextProps) { const words = - props.children == null - ? [] - : props.children.toString().split(/(?:(?!\u00A0+)\s+)/); + props.children == null ? [] : props.children.toString().split(/(?:(?!\u00A0+)\s+)/); this.setState({ wordsByLines: [{ words }] }); } - calculateWordsByLines( - wordsWithWidth: WordWithWidth[], - spaceWidth: number, - lineWidth?: number - ) { + calculateWordsByLines(wordsWithWidth: WordWithWidth[], spaceWidth: number, lineWidth?: number) { const { scaleToFit } = this.props; - return wordsWithWidth.reduce( - (result: WordsWithWidth[], { word, width }) => { - const currentLine = result[result.length - 1]; - - if ( - currentLine && - (lineWidth == null || - scaleToFit || - (currentLine.width || 0) + width + spaceWidth < lineWidth) - ) { - // Word can be added to an existing line - currentLine.words.push(word); - currentLine.width = currentLine.width || 0; - currentLine.width += width + spaceWidth; - } else { - // Add first word to line or word is too long to scaleToFit on existing line - const newLine = { words: [word], width }; - result.push(newLine); - } + return wordsWithWidth.reduce((result: WordsWithWidth[], { word, width }) => { + const currentLine = result[result.length - 1]; + + if ( + currentLine && + (lineWidth == null || + scaleToFit || + (currentLine.width || 0) + width + spaceWidth < lineWidth) + ) { + // Word can be added to an existing line + currentLine.words.push(word); + currentLine.width = currentLine.width || 0; + currentLine.width += width + spaceWidth; + } else { + // Add first word to line or word is too long to scaleToFit on existing line + const newLine = { words: [word], width }; + result.push(newLine); + } - return result; - }, - [] - ); + return result; + }, []); } render() { @@ -207,39 +195,22 @@ class Text extends React.Component { // Cannot render if x or y is invalid if (!isValidXOrY(x) || !isValidXOrY(y)) { - return ( - - ); + return ; } let startDy: string | undefined; - if (verticalAnchor === "start") { + if (verticalAnchor === 'start') { startDy = reduceCSSCalc(`calc(${capHeight})`); - } else if (verticalAnchor === "middle") { + } else if (verticalAnchor === 'middle') { startDy = reduceCSSCalc( - `calc(${(wordsByLines.length - 1) / - 2} * -${lineHeight} + (${capHeight} / 2))` + `calc(${(wordsByLines.length - 1) / 2} * -${lineHeight} + (${capHeight} / 2))`, ); } else { - startDy = reduceCSSCalc( - `calc(${wordsByLines.length - 1} * -${lineHeight})` - ); + startDy = reduceCSSCalc(`calc(${wordsByLines.length - 1} * -${lineHeight})`); } const transforms = []; - if ( - isNumber(x) && - isNumber(y) && - isNumber(width) && - scaleToFit && - wordsByLines.length > 0 - ) { + if (isNumber(x) && isNumber(y) && isNumber(width) && scaleToFit && wordsByLines.length > 0) { const lineWidth = wordsByLines[0].width || 1; const sx = width / lineWidth; const sy = sx; @@ -251,28 +222,20 @@ class Text extends React.Component { transforms.push(`rotate(${angle}, ${x}, ${y})`); } - const transform = transforms.length > 0 ? transforms.join(" ") : undefined; + const transform = transforms.length > 0 ? transforms.join(' ') : undefined; const text = wordsByLines.map((line, index) => ( - - {line.words.join(" ")} + + {line.words.join(' ')} )); return ( - + {textPath && } - {textPath ? ( - {text} - ) : ( - text - )} + {textPath ? ( + {text} + ) : text} ); From 46cdf3c54a55f9a1d2a56c11574fa075fbc2868d Mon Sep 17 00:00:00 2001 From: Gabriel Restori Soares Date: Wed, 25 Nov 2020 18:09:58 +0100 Subject: [PATCH 8/9] Fix formatting --- packages/visx-text/test/Text.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/visx-text/test/Text.test.tsx b/packages/visx-text/test/Text.test.tsx index 4fec521bb..db7894be3 100644 --- a/packages/visx-text/test/Text.test.tsx +++ b/packages/visx-text/test/Text.test.tsx @@ -163,7 +163,7 @@ describe('', () => { expect(getVerticalOffset(wrapper)).toBe('0.71em'); }); it('Should render textPath when textPath is passed', () => { - const wrapper = mount(Text path test); + const wrapper = mount(Text path test); const textPath = wrapper.find('textPath'); const path = wrapper.find('path'); From e6d3807dddb101869d10c9fab34131035c30e7d8 Mon Sep 17 00:00:00 2001 From: Gabriel Restori Soares Date: Thu, 26 Nov 2020 18:44:27 +0100 Subject: [PATCH 9/9] Fix linting --- packages/visx-text/src/Text.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/visx-text/src/Text.tsx b/packages/visx-text/src/Text.tsx index 467e5adb6..2dfa86ba2 100644 --- a/packages/visx-text/src/Text.tsx +++ b/packages/visx-text/src/Text.tsx @@ -1,7 +1,7 @@ import React from 'react'; import reduceCSSCalc from 'reduce-css-calc'; -import getStringWidth from './util/getStringWidth'; import uniqueId from 'lodash/uniqueId'; +import getStringWidth from './util/getStringWidth'; const SVG_STYLE = { overflow: 'visible' }; @@ -101,7 +101,7 @@ class Text extends React.Component { this.state = { wordsByLines: [], textPathId: props.textPath ? uniqueId('text-path-') : '', - } + }; } componentDidMount() { @@ -224,7 +224,7 @@ class Text extends React.Component { const transform = transforms.length > 0 ? transforms.join(' ') : undefined; const text = wordsByLines.map((line, index) => ( - + {line.words.join(' ')} )); @@ -233,9 +233,7 @@ class Text extends React.Component { {textPath && } - {textPath ? ( - {text} - ) : text} + {textPath ? {text} : text} );