A lightweight 2D graphics library providing canvas and SVG rendering for Apache ECharts
ZRender provides comprehensive text rendering capabilities with rich typography support and efficient bitmap image display. The text system supports multi-line text, rich formatting, alignment options, and advanced features like text truncation and custom styling.
The primary class for rendering text content:
class Text extends Displayable {
constructor(opts: TextProps);
style: TextStyleProps;
}
interface TextProps extends DisplayableProps {
style?: TextStyleProps;
}
interface TextState {
style?: Partial<TextStyleProps>;
}interface TextStyleProps {
// Content and basic typography
text?: string;
fontSize?: number;
fontFamily?: string;
fontStyle?: 'normal' | 'italic' | 'oblique';
fontWeight?: string | number;
// Text positioning and alignment
textAlign?: 'left' | 'center' | 'right';
textVerticalAlign?: 'top' | 'middle' | 'bottom';
textBaseline?: 'top' | 'middle' | 'bottom' | 'alphabetic' | 'ideographic' | 'hanging';
// Visual appearance
fill?: string | LinearGradient | RadialGradient | Pattern;
stroke?: string;
lineWidth?: number;
opacity?: number;
// Text shadows
textShadowBlur?: number;
textShadowColor?: string;
textShadowOffsetX?: number;
textShadowOffsetY?: number;
// Layout and spacing
width?: number;
height?: number;
textPadding?: number | number[];
textLineHeight?: number;
// Rich text formatting
rich?: Record<string, TextStyleProps>;
// Text truncation
truncate?: {
outerWidth?: number;
outerHeight?: number;
ellipsis?: string;
placeholder?: string;
};
// Advanced styling
textBorderColor?: string;
textBorderWidth?: number;
textBackgroundColor?: string;
backgroundColor?: string;
borderColor?: string;
borderWidth?: number;
borderRadius?: number;
padding?: number | number[];
// Rendering effects
blend?: string;
}Text span element for rich text components:
class TSpan extends Displayable {
constructor(opts: TSpanProps);
style: TSpanStyleProps;
}
interface TSpanProps extends DisplayableProps {
style?: TSpanStyleProps;
}
interface TSpanStyleProps extends TextStyleProps {
x?: number;
y?: number;
text?: string;
}
interface TSpanState {
style?: Partial<TSpanStyleProps>;
}Display bitmap images with complete control over positioning and effects:
class Image extends Displayable {
constructor(opts: ImageProps);
style: ImageStyleProps;
}
interface ImageProps extends DisplayableProps {
style?: ImageStyleProps;
}
interface ImageState {
style?: Partial<ImageStyleProps>;
}interface ImageStyleProps {
// Image source
image?: string | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement;
// Positioning and sizing
x?: number;
y?: number;
width?: number;
height?: number;
// Source cropping (sprite/atlas support)
sx?: number; // Source x position
sy?: number; // Source y position
sWidth?: number; // Source width
sHeight?: number; // Source height
// Visual effects
opacity?: number;
shadowBlur?: number;
shadowColor?: string;
shadowOffsetX?: number;
shadowOffsetY?: number;
// Advanced rendering
blend?: string;
globalCompositeOperation?: string;
}import { Text } from "zrender";
// Simple text
const simpleText = new Text({
style: {
text: 'Hello ZRender!',
fontSize: 24,
fontFamily: 'Arial, sans-serif',
fill: '#2d3436',
textAlign: 'center'
},
position: [200, 100]
});
// Styled text with shadow
const styledText = new Text({
style: {
text: 'Styled Text',
fontSize: 32,
fontWeight: 'bold',
fill: '#74b9ff',
stroke: '#0984e3',
lineWidth: 2,
textShadowBlur: 5,
textShadowColor: 'rgba(0, 0, 0, 0.3)',
textShadowOffsetX: 2,
textShadowOffsetY: 2
},
position: [200, 200]
});
zr.add(simpleText);
zr.add(styledText);import { Text } from "zrender";
// Multi-line text with layout control
const multilineText = new Text({
style: {
text: 'This is a long text that will wrap to multiple lines when the width is constrained.',
fontSize: 16,
fontFamily: 'Arial, sans-serif',
fill: '#2d3436',
width: 250, // Constrain width for wrapping
textLineHeight: 20, // Line height
textAlign: 'left',
textVerticalAlign: 'top',
padding: [10, 15], // Internal padding
backgroundColor: '#f8f9fa',
borderColor: '#dee2e6',
borderWidth: 1,
borderRadius: 5
},
position: [50, 50]
});
// Text with specific alignment
const alignedText = new Text({
style: {
text: 'Center Aligned\nMultiple Lines\nOf Text',
fontSize: 18,
fill: '#6c757d',
textAlign: 'center',
textVerticalAlign: 'middle',
width: 200,
height: 100,
backgroundColor: '#e9ecef',
borderRadius: 8
},
position: [350, 50]
});
zr.add(multilineText);
zr.add(alignedText);import { Text } from "zrender";
// Rich text with multiple styles
const richText = new Text({
style: {
text: '{title|ZRender Graphics}\n{subtitle|Powerful 2D Rendering}\n{body|Create beautiful graphics with ease}\n{link|Learn More →}',
rich: {
title: {
fontSize: 28,
fontWeight: 'bold',
fill: '#2d3436',
textAlign: 'center'
},
subtitle: {
fontSize: 16,
fill: '#74b9ff',
fontStyle: 'italic',
textAlign: 'center',
textPadding: [5, 0, 10, 0]
},
body: {
fontSize: 14,
fill: '#636e72',
textAlign: 'center',
textPadding: [0, 0, 15, 0]
},
link: {
fontSize: 14,
fill: '#00b894',
fontWeight: 'bold',
textAlign: 'center',
textBorderColor: '#00b894',
textBorderWidth: 1,
textPadding: [5, 10],
textBackgroundColor: 'rgba(0, 184, 148, 0.1)',
borderRadius: 3
}
},
width: 300,
textAlign: 'center'
},
position: [50, 250]
});
zr.add(richText);import { Text } from "zrender";
// Text with ellipsis truncation
const truncatedText = new Text({
style: {
text: 'This is a very long text that will be truncated with ellipsis when it exceeds the specified width',
fontSize: 14,
fill: '#2d3436',
width: 200,
truncate: {
outerWidth: 200,
ellipsis: '...',
placeholder: 'No content'
}
},
position: [400, 250]
});
// Text with custom truncation
const customTruncatedText = new Text({
style: {
text: 'Another long text example',
fontSize: 16,
fill: '#e17055',
width: 150,
truncate: {
outerWidth: 150,
ellipsis: ' [more]',
placeholder: '[empty]'
}
},
position: [400, 300]
});
zr.add(truncatedText);
zr.add(customTruncatedText);import { Text, LinearGradient, Pattern } from "zrender";
// Gradient fill text
const gradientText = new Text({
style: {
text: 'GRADIENT',
fontSize: 48,
fontWeight: 'bold',
fill: new LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#ff7675' },
{ offset: 0.5, color: '#fd79a8' },
{ offset: 1, color: '#fdcb6e' }
]),
stroke: '#2d3436',
lineWidth: 2
},
position: [50, 400]
});
// Pattern fill text (if you have a pattern image)
const createTextPattern = () => {
const canvas = document.createElement('canvas');
canvas.width = 20;
canvas.height = 20;
const ctx = canvas.getContext('2d')!;
// Create diagonal stripes pattern
ctx.strokeStyle = '#74b9ff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(20, 20);
ctx.moveTo(0, 20);
ctx.lineTo(20, 0);
ctx.stroke();
return new Pattern(canvas, 'repeat');
};
const patternText = new Text({
style: {
text: 'PATTERN',
fontSize: 48,
fontWeight: 'bold',
fill: createTextPattern(),
stroke: '#0984e3',
lineWidth: 1
},
position: [350, 400]
});
zr.add(gradientText);
zr.add(patternText);import { Image } from "zrender";
// Display image from URL
const imageFromUrl = new Image({
style: {
image: 'https://example.com/image.jpg',
x: 50,
y: 50,
width: 200,
height: 150
}
});
// Display image from HTMLImageElement
const createImageElement = (src: string) => {
const img = new Image();
img.src = src;
return img;
};
const imageFromElement = new Image({
style: {
image: createImageElement('path/to/image.png'),
x: 300,
y: 50,
width: 150,
height: 150,
opacity: 0.8
}
});
zr.add(imageFromUrl);
zr.add(imageFromElement);import { Image } from "zrender";
// Image with shadow effect
const shadowImage = new Image({
style: {
image: 'path/to/image.jpg',
x: 50,
y: 250,
width: 120,
height: 120,
shadowBlur: 15,
shadowColor: 'rgba(0, 0, 0, 0.4)',
shadowOffsetX: 5,
shadowOffsetY: 5
}
});
// Sprite/Atlas usage (cropping from larger image)
const spriteImage = new Image({
style: {
image: 'path/to/spritesheet.png',
x: 200,
y: 250,
width: 64,
height: 64,
// Crop from sprite sheet
sx: 128, // Source x in spritesheet
sy: 64, // Source y in spritesheet
sWidth: 64, // Source width
sHeight: 64 // Source height
}
});
// Animated image scaling
const scalingImage = new Image({
style: {
image: 'path/to/image.jpg',
x: 350,
y: 250,
width: 80,
height: 80
}
});
// Animate image scaling on hover
scalingImage.on('mouseover', () => {
scalingImage.animate('style')
.when(300, { width: 120, height: 120, x: 330, y: 230 })
.start('easeOutQuad');
});
scalingImage.on('mouseout', () => {
scalingImage.animate('style')
.when(300, { width: 80, height: 80, x: 350, y: 250 })
.start('easeOutQuad');
});
zr.add(shadowImage);
zr.add(spriteImage);
zr.add(scalingImage);import { Image } from "zrender";
// Use canvas as image source
const createCanvasImage = () => {
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
const ctx = canvas.getContext('2d')!;
// Draw something on canvas
const gradient = ctx.createLinearGradient(0, 0, 100, 100);
gradient.addColorStop(0, '#ff7675');
gradient.addColorStop(1, '#74b9ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 100, 100);
ctx.fillStyle = '#ffffff';
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.fillText('Canvas', 50, 55);
return canvas;
};
const canvasImage = new Image({
style: {
image: createCanvasImage(),
x: 50,
y: 400,
width: 100,
height: 100
}
});
// Use video as image source (for video frames)
const videoImage = new Image({
style: {
image: document.getElementById('videoElement') as HTMLVideoElement,
x: 200,
y: 400,
width: 160,
height: 90
}
});
zr.add(canvasImage);
// zr.add(videoImage); // Only if video element existsimport { Text, Image, Group } from "zrender";
// Create interactive card with text and image
const createInteractiveCard = (title: string, description: string, imageSrc: string, x: number, y: number) => {
const card = new Group({
position: [x, y]
});
// Background
const background = new Rect({
shape: { x: 0, y: 0, width: 250, height: 150, r: 8 },
style: {
fill: '#ffffff',
stroke: '#dee2e6',
lineWidth: 1,
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.1)'
}
});
// Image
const image = new Image({
style: {
image: imageSrc,
x: 15,
y: 15,
width: 60,
height: 60
}
});
// Title text
const titleText = new Text({
style: {
text: title,
fontSize: 18,
fontWeight: 'bold',
fill: '#2d3436',
textAlign: 'left'
},
position: [90, 25]
});
// Description text
const descText = new Text({
style: {
text: description,
fontSize: 14,
fill: '#636e72',
width: 145,
textAlign: 'left'
},
position: [90, 50]
});
// Build card
card.add(background);
card.add(image);
card.add(titleText);
card.add(descText);
// Add hover effects
card.on('mouseover', () => {
background.animate('style')
.when(200, { shadowBlur: 20, fill: '#f8f9fa' })
.start();
});
card.on('mouseout', () => {
background.animate('style')
.when(200, { shadowBlur: 10, fill: '#ffffff' })
.start();
});
return card;
};
// Create interactive cards
const card1 = createInteractiveCard(
'ZRender Graphics',
'Powerful 2D rendering library for creating interactive visualizations.',
'path/to/zrender-icon.png',
50, 500
);
const card2 = createInteractiveCard(
'Canvas Rendering',
'High-performance canvas-based rendering with full feature support.',
'path/to/canvas-icon.png',
350, 500
);
zr.add(card1);
zr.add(card2);import { Text } from "zrender";
// Dynamic text sizing based on content
const createAdaptiveText = (content: string, maxWidth: number) => {
let fontSize = 20;
let textElement: Text;
// Function to estimate text width (approximate)
const estimateTextWidth = (text: string, size: number) => {
return text.length * size * 0.6; // Rough approximation
};
// Adjust font size to fit width
while (estimateTextWidth(content, fontSize) > maxWidth && fontSize > 8) {
fontSize -= 1;
}
textElement = new Text({
style: {
text: content,
fontSize: fontSize,
fill: '#2d3436',
width: maxWidth,
textAlign: 'center'
}
});
return textElement;
};
// Create texts that adapt to container width
const adaptiveText1 = createAdaptiveText('Short text', 200);
const adaptiveText2 = createAdaptiveText('This is a much longer text that should be sized down', 200);
adaptiveText1.position = [50, 650];
adaptiveText2.position = [300, 650];
zr.add(adaptiveText1);
zr.add(adaptiveText2);Install with Tessl CLI
npx tessl i tessl/npm-zrender