A lightweight 2D graphics library providing canvas and SVG rendering for Apache ECharts
ZRender provides comprehensive utility libraries for mathematical operations, color manipulation, path processing, and platform abstractions. These utilities are essential for graphics programming and provide cross-platform compatibility.
The util namespace provides fundamental utility functions:
namespace util {
// Object utilities
function clone<T>(obj: T): T;
function merge<T, S>(target: T, source: S, overwrite?: boolean): T & S;
function extend<T, S>(target: T, source: S): T & S;
function defaults<T>(target: T, ...sources: Partial<T>[]): T;
// Array utilities
function map<T, R>(arr: T[], cb: (item: T, index: number) => R): R[];
function reduce<T, R>(arr: T[], cb: (prev: R, item: T, index: number) => R, memo?: R): R;
function filter<T>(arr: T[], cb: (item: T, index: number) => boolean): T[];
function find<T>(arr: T[], cb: (item: T, index: number) => boolean): T | undefined;
function indexOf<T>(arr: T[], item: T): number;
// Type checking
function isArray(value: any): value is any[];
function isFunction(value: any): value is Function;
function isString(value: any): value is string;
function isObject(value: any): value is object;
function isNumber(value: any): value is number;
function isBoolean(value: any): value is boolean;
function isDom(value: any): value is Element;
// Value utilities
function retrieve<T>(...args: T[]): T;
function retrieve2<T>(value0: T, value1: T): T;
function retrieve3<T>(value0: T, value1: T, value2: T): T;
function slice<T>(arr: ArrayLike<T>, start?: number, end?: number): T[];
function normalizeCssArray(val: number | number[]): number[];
// String utilities
function trim(str: string): string;
function toCamelCase(str: string): string;
function toHyphenCase(str: string): string;
// Unique ID generation
function guid(): number;
// Performance utilities
function createHashMap<T>(obj?: Record<string, T>): HashMap<T>;
function concatArray<T>(a: T[], b: T[]): T[];
function each<T>(obj: T[] | Record<string, T>, cb: (value: T, key: string | number) => void): void;
}
interface HashMap<T> {
data: Record<string, T>;
get(key: string): T;
set(key: string, value: T): T;
has(key: string): boolean;
removeKey(key: string): void;
}2D transformation matrix utilities:
namespace matrix {
// Matrix creation and manipulation
function create(): number[];
function identity(): number[];
function copy(out: number[], m: number[]): number[];
// Basic transformations
function translate(out: number[], a: number[], v: number[]): number[];
function rotate(out: number[], a: number[], rad: number): number[];
function scale(out: number[], a: number[], v: number[]): number[];
// Matrix operations
function mul(out: number[], a: number[], b: number[]): number[];
function invert(out: number[], a: number[]): number[] | null;
// Point transformations
function applyTransform(out: number[], v: number[], m: number[]): number[];
// Decomposition
function decompose(m: number[], a: number[], translate: number[], rotate: number[], scale: number[]): boolean;
}2D vector mathematics:
namespace vector {
// Vector creation
function create(x?: number, y?: number): number[];
function copy(out: number[], v: number[]): number[];
function clone(v: number[]): number[];
function set(out: number[], a: number, b: number): number[];
// Arithmetic operations
function add(out: number[], a: number[], b: number[]): number[];
function scaleAndAdd(out: number[], a: number[], b: number[], scale: number): number[];
function sub(out: number[], a: number[], b: number[]): number[];
function len(v: number[]): number;
function lenSquare(v: number[]): number;
function mul(out: number[], a: number[], b: number[]): number[];
function div(out: number[], a: number[], b: number[]): number[];
function dot(a: number[], b: number[]): number;
function scale(out: number[], a: number[], b: number): number[];
function normalize(out: number[], a: number[]): number[];
// Distance and angle calculations
function distance(a: number[], b: number[]): number;
function distanceSquare(a: number[], b: number[]): number;
function negate(out: number[], a: number[]): number[];
function lerp(out: number[], a: number[], b: number[], t: number): number[];
// Geometric utilities
function applyTransform(out: number[], v: number[], m: number[]): number[];
function min(out: number[], a: number[], b: number[]): number[];
function max(out: number[], a: number[], b: number[]): number[];
}Color parsing, conversion, and manipulation:
namespace color {
// Color parsing
function parse(colorStr: string): number[] | null;
function rgba2String(rgba: number[]): string;
// Color format conversion
function toHex(color: string): string;
function toRGB(color: string): string;
function toHSL(color: string): string;
function toHSV(color: string): string;
// Color space conversions
function rgb2hsv(r: number, g: number, b: number): number[];
function hsv2rgb(h: number, s: number, v: number): number[];
function rgb2hsl(r: number, g: number, b: number): number[];
function hsl2rgb(h: number, s: number, l: number): number[];
// Color interpolation
function lerp(t: number, a: string, b: string, out?: number[]): string;
function lerpArray(t: number, a: number[], b: number[], out?: number[]): number[];
// Color analysis
function lum(color: string, backgroundLum?: number): number;
function random(): string;
// Color modification
function modifyHSL(color: string, h?: number, s?: number, l?: number): string;
function modifyAlpha(color: string, alpha: number): string;
// Palette generation
function stringify(arrColor: number[], type: 'rgba' | 'rgb'): string;
}SVG path manipulation and processing:
namespace path {
// Path parsing and creation
function createFromString(str: string): PathProxy;
function extendFromString(path: PathProxy, str: string): void;
function mergePath(path1: PathProxy, path2: PathProxy): PathProxy;
// Path transformation
function transformPath(path: PathProxy, m: number[]): void;
// Path analysis
function getPathBoundingRect(path: PathProxy): BoundingRect;
function getPathLength(path: PathProxy): number;
function getPointAtPath(path: PathProxy, percent: number): number[];
// Path utilities
function clone(path: PathProxy): PathProxy;
function parseSVGPath(str: string): PathProxy;
function normalizeArcPath(path: PathProxy): void;
}
interface PathProxy {
data: number[];
len(): number;
setData(data: ArrayLike<number>): void;
appendPath(path: PathProxy): void;
addData(cmd: number, ...args: number[]): void;
moveTo(x: number, y: number): void;
lineTo(x: number, y: number): void;
bezierCurveTo(x1: number, y1: number, x2: number, y2: number, x: number, y: number): void;
quadraticCurveTo(x1: number, y1: number, x: number, y: number): void;
arc(cx: number, cy: number, r: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void;
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void;
rect(x: number, y: number, w: number, h: number): void;
closePath(): void;
}Advanced path morphing and interpolation:
namespace morph {
// Path morphing
function morphPath(from: string, to: string, animationOpts?: any): string;
function getPathKey(path: string): string;
function morphingPathKey(path: string): string;
// Combined morphing
function combineMorphing(morphList: any[]): any;
function isCombineMorphing(obj: any): boolean;
// Morphing utilities
function pathToAbsolute(path: string): string;
function pathToBezier(path: string): number[][];
}Platform-specific API configuration:
function setPlatformAPI(platformAPI: PlatformAPI): void;
interface PlatformAPI {
createCanvas?: () => HTMLCanvasElement;
measureText?: (text: string, font: string) => { width: number };
loadImage?: (src: string, onload: () => void, onerror: () => void) => HTMLImageElement;
// Platform detection
$override?: {
createCanvas?: () => HTMLCanvasElement;
measureText?: (text: string, font: string) => { width: number };
loadImage?: (src: string, onload: () => void, onerror: () => void) => HTMLImageElement;
};
}Additional geometric calculations and utilities:
// Point and rectangle utilities
interface Point {
x: number;
y: number;
}
interface PointLike {
x: number;
y: number;
}
interface RectLike {
x: number;
y: number;
width: number;
height: number;
}
class BoundingRect {
x: number;
y: number;
width: number;
height: number;
constructor(x: number, y: number, width: number, height: number);
// Geometric operations
union(other: BoundingRect): void;
intersect(other: BoundingRect): void;
contain(x: number, y: number): boolean;
clone(): BoundingRect;
copy(other: BoundingRect): void;
plain(): RectLike;
// Static utilities
static create(rect: RectLike): BoundingRect;
static copy(target: BoundingRect, source: BoundingRect): void;
static applyTransform(target: BoundingRect, source: BoundingRect, m: number[]): void;
}
class OrientedBoundingRect {
cx: number;
cy: number;
width: number;
height: number;
rotation: number;
constructor();
// Geometric operations
intersect(other: OrientedBoundingRect): boolean;
contain(x: number, y: number): boolean;
clone(): OrientedBoundingRect;
// Static utilities
static fromPoints(points: number[][]): OrientedBoundingRect;
}import { util, matrix, vector, color } from "zrender";
// Object manipulation
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = util.merge({}, obj1, obj2); // { a: 1, b: 3, c: 4 }
// Array operations
const numbers = [1, 2, 3, 4, 5];
const doubled = util.map(numbers, x => x * 2); // [2, 4, 6, 8, 10]
const evens = util.filter(numbers, x => x % 2 === 0); // [2, 4]
// Type checking
if (util.isArray(someValue)) {
console.log('It is an array');
}
// Unique IDs
const uniqueId = util.guid(); // Returns unique numberimport { matrix } from "zrender";
// Create transformation matrix
const m = matrix.identity();
// Apply transformations
matrix.translate(m, m, [100, 50]); // Translate by (100, 50)
matrix.rotate(m, m, Math.PI / 4); // Rotate 45 degrees
matrix.scale(m, m, [1.5, 1.5]); // Scale by 1.5x
// Transform a point
const point = [0, 0];
const transformedPoint = matrix.applyTransform([], point, m);
console.log('Transformed point:', transformedPoint);
// Matrix multiplication
const m1 = matrix.identity();
const m2 = matrix.identity();
matrix.scale(m1, m1, [2, 2]);
matrix.rotate(m2, m2, Math.PI / 2);
const combined = matrix.mul([], m1, m2);import { vector } from "zrender";
// Vector operations
const v1 = vector.create(3, 4);
const v2 = vector.create(1, 2);
const sum = vector.add([], v1, v2); // [4, 6]
const length = vector.len(v1); // 5
const normalized = vector.normalize([], v1); // [0.6, 0.8]
const distance = vector.distance(v1, v2); // ~2.83
// Interpolation
const lerped = vector.lerp([], v1, v2, 0.5); // Midpoint between v1 and v2
// Dot product for angle calculations
const dotProduct = vector.dot(v1, v2); // 11
const angle = Math.acos(dotProduct / (vector.len(v1) * vector.len(v2)));import { color } from "zrender";
// Color parsing and conversion
const rgbaArray = color.parse('#ff6b6b'); // [255, 107, 107, 1]
const hexColor = color.toHex('rgb(255, 107, 107)'); // '#ff6b6b'
const hslColor = color.toHSL('#ff6b6b'); // 'hsl(0, 100%, 71%)'
// Color interpolation
const startColor = '#ff0000';
const endColor = '#00ff00';
const midColor = color.lerp(0.5, startColor, endColor); // Blend colors
// Color modification
const brighterColor = color.modifyHSL('#3498db', 0, 0.2, 0.1); // Increase saturation and lightness
const transparentColor = color.modifyAlpha('#3498db', 0.5); // 50% opacity
// Color analysis
const luminance = color.lum('#3498db'); // Get relative luminance
const randomColor = color.random(); // Generate random color
// Palette generation
const palette = [];
for (let i = 0; i < 5; i++) {
palette.push(color.lerp(i / 4, '#ff6b6b', '#4ecdc4'));
}import { path, parseSVG } from "zrender";
// Create path from SVG string
const svgPath = "M 10 10 L 100 10 L 100 100 L 10 100 Z";
const pathProxy = path.createFromString(svgPath);
// Get path properties
const bounds = path.getPathBoundingRect(pathProxy);
const length = path.getPathLength(pathProxy);
// Get point at specific position along path
const midPoint = path.getPointAtPath(pathProxy, 0.5); // 50% along path
// Transform path
const transformMatrix = matrix.identity();
matrix.scale(transformMatrix, transformMatrix, [2, 2]);
path.transformPath(pathProxy, transformMatrix);
// Parse SVG content
const svgElement = document.querySelector('svg')!;
const elements = parseSVG(svgElement);
// Returns ZRender elements created from SVGimport { util, BoundingRect } from "zrender";
// Custom utility function using ZRender utilities
function createBoundedElements(bounds: RectLike, count: number) {
const elements = [];
for (let i = 0; i < count; i++) {
// Use util functions for safe operations
const x = bounds.x + Math.random() * bounds.width;
const y = bounds.y + Math.random() * bounds.height;
const size = 10 + Math.random() * 20;
const circle = new Circle({
shape: { cx: x, cy: y, r: size },
style: {
fill: color.random(),
opacity: 0.7
}
});
elements.push(circle);
}
return elements;
}
// Use the custom utility
const canvasBounds = new BoundingRect(0, 0, 800, 600);
const randomElements = createBoundedElements(canvasBounds.plain(), 50);
randomElements.forEach(el => zr.add(el));import { util } from "zrender";
// Efficient array operations
const largeArray = new Array(10000).fill(0).map((_, i) => i);
// Use ZRender's optimized utilities instead of native methods when possible
const processedData = util.map(largeArray, (value, index) => {
return { id: index, value: value * 2, processed: true };
});
// Efficient object creation
const hashMap = util.createHashMap<number>();
for (let i = 0; i < 1000; i++) {
hashMap.set(`key${i}`, i);
}
// Memory-efficient cloning
const originalData = {
positions: [[0, 0], [100, 100], [200, 200]],
colors: ['#ff0000', '#00ff00', '#0000ff'],
metadata: { created: Date.now() }
};
const clonedData = util.clone(originalData); // Deep cloneParse SVG markup into ZRender elements for rendering and manipulation:
/**
* Parse SVG markup into ZRender graphics elements
* @param svg - SVG string, Document, or SVGElement to parse
* @param options - Parsing configuration options
* @returns Parsed SVG result with root group and metadata
*/
function parseSVG(svg: string | Document | SVGElement, options?: SVGParserOption): SVGParserResult;
interface SVGParserOption {
width?: number; // Default width if not specified in SVG
height?: number; // Default height if not specified in SVG
ignoreViewBox?: boolean; // Whether to ignore SVG viewBox
}
interface SVGParserResult {
root: Group; // Root group containing all parsed elements
width: number; // Computed viewport width
height: number; // Computed viewport height
viewBoxRect: RectLike; // ViewBox rectangle if specified
viewBoxTransform: { // Transform from viewBox to viewport
scale: number[];
position: number[];
};
}Usage Examples:
import { parseSVG } from "zrender";
// Parse SVG string
const svgString = `
<svg width="200" height="200">
<circle cx="100" cy="100" r="50" fill="red"/>
<rect x="50" y="50" width="100" height="100" fill="blue" opacity="0.5"/>
</svg>
`;
const result = parseSVG(svgString, {
width: 400,
height: 400
});
// Add parsed elements to ZRender instance
zr.add(result.root);
// Access parsed elements
console.log(`SVG dimensions: ${result.width}x${result.height}`);
console.log(`Root contains ${result.root.children().length} elements`);Development utility for visualizing dirty rectangles in canvas rendering:
/**
* Show debug visualization of dirty rectangles on canvas
* @param zr - ZRender instance to debug
* @param options - Debug visualization options
*/
function showDebugDirtyRect(zr: ZRender, options?: DebugDirtyRectOptions): void;
interface DebugDirtyRectOptions {
style?: {
backgroundColor?: string; // Default: 'rgba(0, 0, 255, 0.2)'
border?: string; // Default: '1px solid #00f'
[key: string]: any; // Additional CSS properties
};
autoHideDelay?: number; // Auto-hide delay in milliseconds
}Usage Examples:
import { init, showDebugDirtyRect } from "zrender";
// Initialize ZRender with debug mode
const zr = init(document.getElementById('canvas'));
// Enable dirty rectangle visualization
showDebugDirtyRect(zr, {
style: {
backgroundColor: 'rgba(255, 0, 0, 0.3)',
border: '2px solid #f00'
},
autoHideDelay: 1000
});
// Now dirty rectangles will be highlighted when elements are updated
const circle = new Circle({
shape: { cx: 100, cy: 100, r: 50 },
style: { fill: 'blue' }
});
zr.add(circle);
// This update will show a debug rectangle
circle.attr('shape', { cx: 150, cy: 150 });Install with Tessl CLI
npx tessl i tessl/npm-zrender