A lightweight 2D graphics library providing canvas and SVG rendering for Apache ECharts
ZRender's graphics system is built on a hierarchical structure of elements, with specialized classes for different types of graphics content. All graphics elements inherit from the base Element class and support styling, transformations, animations, and event handling.
The foundational class for all graphics objects in ZRender:
class Element {
// Hierarchy management
add(el: Element): void;
remove(el: Element): void;
removeAll(): void;
parent: Group | null;
children(): Element[];
// Transformations
position: number[];
rotation: number;
scale: number[];
origin: number[];
// Display properties
invisible: boolean;
ignore: boolean;
silent: boolean;
zlevel: number;
z: number;
// Animation
animate(props?: string): Animator;
stopAnimation(forwardToLast?: boolean): this;
// Events
on(eventName: string, handler: ElementEventCallback, context?: any): this;
off(eventName?: string, handler?: ElementEventCallback): void;
trigger(eventName: string, event?: any): void;
// Utilities
getBoundingRect(): BoundingRect;
clone(): Element;
dispose(): void;
}Base class for visible elements that can be rendered:
class Displayable extends Element {
style: any;
dirty(): void;
styleChanged(): boolean;
useStyle(obj: any): void;
getBoundingRect(): BoundingRect;
}
interface DisplayableProps extends ElementProps {
style?: any;
zlevel?: number;
z?: number;
culling?: boolean;
cursor?: string;
}Container element for organizing and transforming multiple elements as a unit:
class Group extends Element {
children(): Element[];
childAt(idx: number): Element;
childOfName(name: string): Element;
childCount(): number;
eachChild<T>(cb: (this: T, child: Element, index: number) => void, context?: T): void;
traverse<T>(cb: (this: T, el: Element) => boolean | void, context?: T): void;
getBoundingRect(includeChildren?: Element[]): BoundingRect;
}
interface GroupProps extends ElementProps {
// Groups primarily use inherited properties from ElementProps
}Base class for vector graphics elements (shapes):
class Path extends Displayable {
shape: any;
buildPath(ctx: CanvasRenderingContext2D | SVGPathElement, shape: any): void;
getBoundingRect(): BoundingRect;
contain(x: number, y: number): boolean;
}
interface PathProps extends DisplayableProps {
shape?: any;
style?: PathStyleProps;
}
interface PathStyleProps {
fill?: string | LinearGradient | RadialGradient | Pattern;
stroke?: string;
lineWidth?: number;
lineDash?: number[];
lineDashOffset?: number;
lineCap?: 'butt' | 'round' | 'square';
lineJoin?: 'bevel' | 'round' | 'miter';
miterLimit?: number;
opacity?: number;
fillOpacity?: number;
strokeOpacity?: number;
shadowBlur?: number;
shadowColor?: string;
shadowOffsetX?: number;
shadowOffsetY?: number;
}Specialized element for text rendering:
class Text extends Displayable {
style: TextStyleProps;
}
interface TextProps extends DisplayableProps {
style?: TextStyleProps;
}
interface TextStyleProps {
text?: string;
fontSize?: number;
fontFamily?: string;
fontStyle?: 'normal' | 'italic' | 'oblique';
fontWeight?: string | number;
fill?: string | LinearGradient | RadialGradient | Pattern;
stroke?: string;
lineWidth?: number;
textAlign?: 'left' | 'center' | 'right';
textVerticalAlign?: 'top' | 'middle' | 'bottom';
textBaseline?: 'top' | 'middle' | 'bottom' | 'alphabetic' | 'ideographic' | 'hanging';
opacity?: number;
textShadowBlur?: number;
textShadowColor?: string;
textShadowOffsetX?: number;
textShadowOffsetY?: number;
width?: number;
height?: number;
textPadding?: number | number[];
textLineHeight?: number;
rich?: Record<string, TextStyleProps>;
truncate?: {
outerWidth?: number;
outerHeight?: number;
ellipsis?: string;
placeholder?: string;
};
blend?: string;
}
interface TextState {
style?: Partial<TextStyleProps>;
}Text span element for rich text formatting:
class TSpan extends Displayable {
style: TSpanStyleProps;
}
interface TSpanProps extends DisplayableProps {
style?: TSpanStyleProps;
}
interface TSpanStyleProps extends TextStyleProps {
x?: number;
y?: number;
text?: string;
}
interface TSpanState {
style?: Partial<TSpanStyleProps>;
}Element for displaying bitmap images:
class Image extends Displayable {
style: ImageStyleProps;
}
interface ImageProps extends DisplayableProps {
style?: ImageStyleProps;
}
interface ImageStyleProps {
image?: string | HTMLImageElement | HTMLCanvasElement;
x?: number;
y?: number;
width?: number;
height?: number;
sx?: number; // Source x
sy?: number; // Source y
sWidth?: number; // Source width
sHeight?: number; // Source height
opacity?: number;
shadowBlur?: number;
shadowColor?: string;
shadowOffsetX?: number;
shadowOffsetY?: number;
}
interface ImageState {
style?: Partial<ImageStyleProps>;
}Element for creating complex paths from multiple sub-paths:
class CompoundPath extends Path {
shape: CompoundPathShape;
}
interface CompoundPathShape {
paths: Path[];
}Special element for progressive/streaming rendering:
class IncrementalDisplayable extends Displayable {
incremental: boolean;
clearIncrementalNewAdded(): void;
markIncrementalNewAdded(): void;
}All graphics elements support these common properties through the ElementProps interface:
interface ElementProps {
// Transform properties
position?: number[]; // [x, y]
rotation?: number; // Rotation in radians
scale?: number[]; // [scaleX, scaleY]
origin?: number[]; // [originX, originY] transform origin
// Display properties
zlevel?: number; // Layer level (affects rendering order)
z?: number; // Z-index within layer
invisible?: boolean; // Hide element
ignore?: boolean; // Ignore in hit testing and rendering
silent?: boolean; // Disable event handling
// Interaction
cursor?: string; // CSS cursor style
draggable?: boolean; // Enable dragging
progressive?: number; // Progressive rendering threshold
// Animation
culling?: boolean; // Enable view culling
rectHover?: boolean; // Use rectangular hover area
useHoverLayer?: boolean; // Render hover state on separate layer
}import { Group, Circle, Text } from "zrender";
// Create a group to contain multiple elements
const group = new Group({
position: [100, 100],
rotation: Math.PI / 4
});
// Create a circle
const circle = new Circle({
shape: { cx: 0, cy: 0, r: 50 },
style: { fill: '#ff0000', stroke: '#000000', lineWidth: 2 },
z: 1
});
// Create text
const text = new Text({
style: {
text: 'Hello ZRender',
fontSize: 16,
fill: '#333333',
textAlign: 'center'
},
position: [0, -60],
z: 2
});
// Build hierarchy
group.add(circle);
group.add(text);
// Add to ZRender instance
zr.add(group);import { Rect } from "zrender";
const rect = new Rect({
shape: { x: 0, y: 0, width: 100, height: 50 },
style: { fill: '#0066cc' },
position: [50, 50]
});
// Animate properties
rect.animate('position')
.when(1000, [300, 200])
.when(2000, [50, 50])
.start();
// Animate shape
rect.animate('shape')
.when(1000, { width: 200, height: 100 })
.start('easeInOut');
// Animate style
rect.animate('style')
.when(500, { fill: '#ff6600' })
.start();import { Circle } from "zrender";
const circle = new Circle({
shape: { cx: 100, cy: 100, r: 30 },
style: { fill: '#00cc66' }
});
// Add event handlers
circle.on('click', (e) => {
console.log('Circle clicked!', e);
});
circle.on('mouseover', (e) => {
circle.animate('style')
.when(200, { fill: '#66ff99' })
.start();
});
circle.on('mouseout', (e) => {
circle.animate('style')
.when(200, { fill: '#00cc66' })
.start();
});
zr.add(circle);import { Group, Rect, Text, Circle } from "zrender";
// Create a button-like component
function createButton(text: string, x: number, y: number) {
const button = new Group({
position: [x, y]
});
const background = new Rect({
shape: { x: 0, y: 0, width: 120, height: 40, r: 5 },
style: {
fill: '#4a90e2',
stroke: '#357abd',
lineWidth: 1
}
});
const label = new Text({
style: {
text: text,
fontSize: 14,
fill: '#ffffff',
textAlign: 'center',
textVerticalAlign: 'middle'
},
position: [60, 20]
});
button.add(background);
button.add(label);
// Add hover effects
button.on('mouseover', () => {
background.animate('style')
.when(150, { fill: '#5aa3f0' })
.start();
});
button.on('mouseout', () => {
background.animate('style')
.when(150, { fill: '#4a90e2' })
.start();
});
return button;
}
// Use the component
const btn1 = createButton('Click Me', 50, 50);
const btn2 = createButton('Cancel', 200, 50);
zr.add(btn1);
zr.add(btn2);Install with Tessl CLI
npx tessl i tessl/npm-zrender