Enhanced SVG Text component with word-wrapping, vertical alignment, rotation, and scale-to-fit capabilities for React visualizations
npx @tessl/cli install tessl/npm-visx--text@3.12.00
# @visx/text
1
2
@visx/text provides an enhanced SVG Text component for React applications that extends the basic SVG text element with advanced text layout capabilities including automatic word-wrapping when width is specified, vertical alignment through the verticalAnchor prop, text rotation via the angle prop, and scale-to-fit functionality.
3
4
## Package Information
5
6
- **Package Name**: @visx/text
7
- **Package Type**: npm
8
- **Language**: TypeScript
9
- **Installation**: `npm install @visx/text`
10
11
## Core Imports
12
13
```typescript
14
import { Text, useText, getStringWidth } from "@visx/text";
15
import type { TextProps, WordsWithWidth, compareFunction } from "@visx/text";
16
```
17
18
For CommonJS:
19
20
```javascript
21
const { Text, useText, getStringWidth } = require("@visx/text");
22
```
23
24
## Basic Usage
25
26
```typescript
27
import React from "react";
28
import { Text } from "@visx/text";
29
30
// Simple text with vertical alignment
31
const App = () => (
32
<svg width={400} height={300}>
33
<Text
34
x={50}
35
y={50}
36
verticalAnchor="start"
37
fontSize={16}
38
fill="black"
39
>
40
Hello world
41
</Text>
42
</svg>
43
);
44
45
// Text with word wrapping
46
const WrappedTextExample = () => (
47
<svg width={400} height={300}>
48
<Text
49
x={50}
50
y={50}
51
width={200}
52
verticalAnchor="start"
53
fontSize={14}
54
fill="blue"
55
>
56
This is a long text that will be wrapped automatically when the width constraint is applied.
57
</Text>
58
</svg>
59
);
60
61
// Rotated and scaled text
62
const AdvancedTextExample = () => (
63
<svg width={400} height={300}>
64
<Text
65
x={200}
66
y={150}
67
width={180}
68
angle={45}
69
scaleToFit="shrink-only"
70
textAnchor="middle"
71
verticalAnchor="middle"
72
fontSize={16}
73
fill="red"
74
>
75
Rotated and fitted text
76
</Text>
77
</svg>
78
);
79
```
80
81
## Architecture
82
83
@visx/text is built around three core components:
84
85
- **Text Component**: The main React component that renders enhanced SVG text elements with advanced layout features
86
- **useText Hook**: Internal hook that handles all text layout calculations including line breaking, positioning, and transforms
87
- **getStringWidth Utility**: Browser-based text measurement function using hidden SVG elements for accurate pixel width calculations
88
89
The architecture follows a separation of concerns where the Text component handles rendering while useText manages all layout computation, and getStringWidth provides the foundational measurement capabilities.
90
91
## Capabilities
92
93
### Text Component
94
95
The main React component providing enhanced SVG text rendering with word-wrapping, alignment, rotation, and scaling features.
96
97
```typescript { .api }
98
/**
99
* Enhanced SVG Text component with advanced layout features
100
* @param props - TextProps configuration object
101
* @returns JSX.Element - SVG element containing positioned text
102
*/
103
function Text(props: TextProps): JSX.Element;
104
105
type TextOwnProps = {
106
/** CSS class name to apply to the SVGText element */
107
className?: string;
108
/** Whether to scale font size to fit the specified width (default: false) */
109
scaleToFit?: boolean | 'shrink-only';
110
/** Rotation angle in degrees */
111
angle?: number;
112
/** Horizontal text anchor position (default: 'start') */
113
textAnchor?: 'start' | 'middle' | 'end' | 'inherit';
114
/** Vertical text anchor position (default: 'end' in useText hook, undefined in Text component) */
115
verticalAnchor?: 'start' | 'middle' | 'end';
116
/** Inline styles for the text (used in text measurement calculations) */
117
style?: React.CSSProperties;
118
/** Ref for the SVG container element */
119
innerRef?: React.Ref<SVGSVGElement>;
120
/** Ref for the text element */
121
innerTextRef?: React.Ref<SVGTextElement>;
122
/** X position of the text */
123
x?: string | number;
124
/** Y position of the text */
125
y?: string | number;
126
/** X offset from the position (default: 0) */
127
dx?: string | number;
128
/** Y offset from the position (default: 0) */
129
dy?: string | number;
130
/** Line height for multi-line text (default: '1em') */
131
lineHeight?: React.SVGAttributes<SVGTSpanElement>['dy'];
132
/** Cap height for vertical alignment calculations (default: '0.71em') */
133
capHeight?: React.SVGAttributes<SVGTSpanElement>['capHeight'];
134
/** Font size */
135
fontSize?: string | number;
136
/** Font family */
137
fontFamily?: string;
138
/** Text fill color */
139
fill?: string;
140
/** Maximum width for word wrapping (approximate, words are not split) */
141
width?: number;
142
/** Text content to render */
143
children?: string | number;
144
};
145
146
type TextProps = TextOwnProps & Omit<React.SVGAttributes<SVGTextElement>, keyof TextOwnProps>;
147
148
/**
149
* Note: TextProps extends all standard SVG text element attributes including:
150
* - Standard SVG styling: stroke, strokeWidth, opacity, etc.
151
* - Event handlers: onClick, onMouseOver, onMouseOut, etc.
152
* - Accessibility attributes: aria-*, role, etc.
153
* Only the custom @visx/text-specific props are documented above.
154
*/
155
```
156
157
**Usage Examples:**
158
159
```typescript
160
import React from "react";
161
import { Text } from "@visx/text";
162
163
// Basic text positioning
164
<Text x={100} y={50} fontSize={16} fill="black">
165
Simple text
166
</Text>
167
168
// Word-wrapped text
169
<Text
170
x={50}
171
y={50}
172
width={200}
173
fontSize={14}
174
verticalAnchor="start"
175
lineHeight="1.2em"
176
>
177
This text will wrap to multiple lines when it exceeds 200 pixels width
178
</Text>
179
180
// Rotated text with scaling
181
<Text
182
x={150}
183
y={100}
184
width={100}
185
angle={30}
186
scaleToFit="shrink-only"
187
textAnchor="middle"
188
verticalAnchor="middle"
189
fontSize={16}
190
>
191
Rotated scaled text
192
</Text>
193
194
// Styled text with refs and standard SVG attributes
195
<Text
196
x={200}
197
y={150}
198
className="custom-text"
199
style={{ fontWeight: 'bold' }}
200
innerRef={svgRef}
201
innerTextRef={textRef}
202
fill="#ff6b6b"
203
stroke="black"
204
strokeWidth={0.5}
205
opacity={0.9}
206
onClick={(e) => console.log('Text clicked')}
207
>
208
Styled text with refs
209
</Text>
210
```
211
212
### Text Layout Hook
213
214
React hook that handles text layout calculations including line breaking, vertical positioning, and transform generation.
215
216
```typescript { .api }
217
/**
218
* Hook for calculating text layout including line breaks and positioning
219
* @param props - TextProps configuration
220
* @returns Object containing layout calculation results
221
*/
222
function useText(props: TextProps): {
223
wordsByLines: WordsWithWidth[];
224
startDy: string;
225
transform: string;
226
};
227
228
interface WordsWithWidth {
229
/** Array of words in this line */
230
words: string[];
231
/** Calculated pixel width of the line (optional) */
232
width?: number;
233
}
234
```
235
236
**Usage Example:**
237
238
```typescript
239
import React from "react";
240
import { useText } from "@visx/text";
241
import type { TextProps } from "@visx/text";
242
243
const CustomTextComponent = (props: TextProps) => {
244
const { wordsByLines, startDy, transform } = useText(props);
245
const { x = 0, fontSize, textAnchor = 'start' } = props;
246
247
return (
248
<svg>
249
<text transform={transform} fontSize={fontSize} textAnchor={textAnchor}>
250
{wordsByLines.map((line, index) => (
251
<tspan
252
key={index}
253
x={x}
254
dy={index === 0 ? startDy : props.lineHeight || '1em'}
255
>
256
{line.words.join(' ')}
257
</tspan>
258
))}
259
</text>
260
</svg>
261
);
262
};
263
```
264
265
### String Width Measurement
266
267
Utility function for measuring the pixel width of text strings using browser SVG measurement.
268
269
```typescript { .api }
270
/**
271
* Measures the pixel width of a text string using SVG measurement
272
* Memoized using lodash.memoize with cache key: `${str}_${JSON.stringify(style)}`
273
* Creates a hidden SVG element with id '__react_svg_text_measurement_id' for measurements
274
* @param str - Text string to measure
275
* @param style - Optional CSS style object to apply during measurement
276
* @returns Pixel width of the text using getComputedTextLength(), or null if measurement fails (non-browser environment, DOM errors)
277
*/
278
function getStringWidth(str: string, style?: object): number | null;
279
```
280
281
**Usage Example:**
282
283
```typescript
284
import { getStringWidth } from "@visx/text";
285
286
// Basic width measurement
287
const width = getStringWidth("Hello world");
288
console.log(width); // e.g., 77.5
289
290
// Width with custom styling
291
const styledWidth = getStringWidth("Bold text", {
292
fontFamily: 'Arial',
293
fontSize: '16px',
294
fontWeight: 'bold'
295
});
296
console.log(styledWidth); // e.g., 89.2
297
298
// Handle potential measurement failures
299
const safeWidth = getStringWidth("Some text") || 0;
300
```
301
302
## Types
303
304
### Comparison Function Type
305
306
```typescript { .api }
307
/**
308
* Generic type for comparison functions used in memoization
309
* @param prev - Previous value (may be undefined)
310
* @param next - Next value to compare
311
* @returns Boolean indicating if values are equal
312
*/
313
type compareFunction<T> = (prev: T | undefined, next: T) => boolean;
314
```
315
316
## Platform Requirements
317
318
- **Browser Environment**: Requires DOM access for text measurement via `document`
319
- **React**: Version 16.3.0 or higher
320
- **SVG Support**: Requires SVG rendering capabilities
321
322
## Error Handling
323
324
- **getStringWidth**: Returns `null` if measurement fails (e.g., in non-browser environments)
325
- **Text Component**: Safely handles invalid x/y coordinates by not rendering text
326
- **useText**: Returns empty wordsByLines array for invalid positioning values
327
328
## Implementation Details
329
330
### Text Processing
331
332
- **Word Splitting**: Text is split using the regex `/(?:(?!\u00A0+)\s+)/` which preserves non-breaking spaces (`\u00A0`) while splitting on other whitespace
333
- **Space Width Calculation**: Uses non-breaking space (`\u00A0`) for space width measurements to ensure consistent spacing
334
- **Line Breaking**: Words are never split - line breaks only occur between complete words
335
- **Width Validation**: X and Y coordinates must be finite numbers or valid strings (for percentage values)
336
337
### Transform Generation
338
339
- **Scale-to-fit**: When enabled, creates a matrix transform using the first line's width as reference
340
- **Rotation**: Applied around the specified x,y coordinates as the rotation center
341
- **Transform Order**: Scaling is applied before rotation in the transform chain
342
343
### SVG Container
344
345
- **Container Element**: Text is wrapped in an SVG element with `overflow: 'visible'` style
346
- **Positioning**: Container uses dx/dy for offset positioning from the base x/y coordinates
347
348
## Performance Considerations
349
350
- **Text Measurement**: getStringWidth is memoized using lodash.memoize with string and style as cache keys
351
- **Layout Calculations**: useText uses React.useMemo to prevent unnecessary recalculations
352
- **DOM Elements**: Hidden SVG measurement element is reused across all getStringWidth calls
353
- **Conditional Processing**: Layout calculations only run when width or scaleToFit props are provided