UNPKG

zrender

Version:

A lightweight graphic library providing 2d draw for Apache ECharts

300 lines (251 loc) 7.73 kB
/** * Group是一个容器,可以插入子节点,Group的变换也会被应用到子节点上 * @module zrender/graphic/Group * @example * const Group = require('zrender/graphic/Group'); * const Circle = require('zrender/graphic/shape/Circle'); * const g = new Group(); * g.position[0] = 100; * g.position[1] = 100; * g.add(new Circle({ * style: { * x: 100, * y: 100, * r: 20, * } * })); * zr.add(g); */ import * as zrUtil from '../core/util'; import Element, { ElementProps } from '../Element'; import BoundingRect from '../core/BoundingRect'; import { MatrixArray } from '../core/matrix'; import Displayable from './Displayable'; import { ZRenderType } from '../zrender'; export interface GroupProps extends ElementProps { } class Group extends Element<GroupProps> { readonly isGroup = true private _children: Element[] = [] constructor(opts?: GroupProps) { super(); this.attr(opts); } /** * Get children reference. */ childrenRef() { return this._children; } /** * Get children copy. */ children() { return this._children.slice(); } /** * 获取指定 index 的儿子节点 */ childAt(idx: number): Element { return this._children[idx]; } /** * 获取指定名字的儿子节点 */ childOfName(name: string): Element { const children = this._children; for (let i = 0; i < children.length; i++) { if (children[i].name === name) { return children[i]; } } } childCount(): number { return this._children.length; } /** * 添加子节点到最后 */ add(child: Element): Group { if (child) { if (child !== this && child.parent !== this) { this._children.push(child); this._doAdd(child); } if (process.env.NODE_ENV !== 'production') { if (child.__hostTarget) { throw 'This elemenet has been used as an attachment'; } } } return this; } /** * 添加子节点在 nextSibling 之前 */ addBefore(child: Element, nextSibling: Element) { if (child && child !== this && child.parent !== this && nextSibling && nextSibling.parent === this) { const children = this._children; const idx = children.indexOf(nextSibling); if (idx >= 0) { children.splice(idx, 0, child); this._doAdd(child); } } return this; } replace(oldChild: Element, newChild: Element) { const idx = zrUtil.indexOf(this._children, oldChild); if (idx >= 0) { this.replaceAt(newChild, idx); } return this; } replaceAt(child: Element, index: number) { const children = this._children; const old = children[index]; if (child && child !== this && child.parent !== this && child !== old) { children[index] = child; old.parent = null; const zr = this.__zr; if (zr) { old.removeSelfFromZr(zr); } this._doAdd(child); } return this; } _doAdd(child: Element) { if (child.parent) { // Parent must be a group (child.parent as Group).remove(child); } child.parent = this; const zr = this.__zr; if (zr && zr !== (child as Group).__zr) { // Only group has __storage child.addSelfToZr(zr); } zr && zr.refresh(); } /** * Remove child * @param child */ remove(child: Element) { const zr = this.__zr; const children = this._children; const idx = zrUtil.indexOf(children, child); if (idx < 0) { return this; } children.splice(idx, 1); child.parent = null; if (zr) { child.removeSelfFromZr(zr); } zr && zr.refresh(); return this; } /** * Remove all children */ removeAll() { const children = this._children; const zr = this.__zr; for (let i = 0; i < children.length; i++) { const child = children[i]; if (zr) { child.removeSelfFromZr(zr); } child.parent = null; } children.length = 0; return this; } /** * 遍历所有子节点 */ eachChild<Context>( cb: (this: Context, el: Element, index?: number) => void, context?: Context ) { const children = this._children; for (let i = 0; i < children.length; i++) { const child = children[i]; cb.call(context, child, i); } return this; } /** * Visit all descendants. * Return false in callback to stop visit descendants of current node */ // TODO Group itself should also invoke the callback. traverse<T>( cb: (this: T, el: Element) => boolean | void, context?: T ) { for (let i = 0; i < this._children.length; i++) { const child = this._children[i]; const stopped = cb.call(context, child); if (child.isGroup && !stopped) { child.traverse(cb, context); } } return this; } addSelfToZr(zr: ZRenderType) { super.addSelfToZr(zr); for (let i = 0; i < this._children.length; i++) { const child = this._children[i]; child.addSelfToZr(zr); } } removeSelfFromZr(zr: ZRenderType) { super.removeSelfFromZr(zr); for (let i = 0; i < this._children.length; i++) { const child = this._children[i]; child.removeSelfFromZr(zr); } } getBoundingRect(includeChildren?: Element[]): BoundingRect { // TODO Caching const tmpRect = new BoundingRect(0, 0, 0, 0); const children = includeChildren || this._children; const tmpMat: MatrixArray = []; let rect = null; for (let i = 0; i < children.length; i++) { const child = children[i]; // TODO invisible? if (child.ignore || (child as Displayable).invisible) { continue; } const childRect = child.getBoundingRect(); const transform = child.getLocalTransform(tmpMat); // TODO // The boundingRect cacluated by transforming original // rect may be bigger than the actual bundingRect when rotation // is used. (Consider a circle rotated aginst its center, where // the actual boundingRect should be the same as that not be // rotated.) But we can not find better approach to calculate // actual boundingRect yet, considering performance. if (transform) { BoundingRect.applyTransform(tmpRect, childRect, transform); rect = rect || tmpRect.clone(); rect.union(tmpRect); } else { rect = rect || childRect.clone(); rect.union(childRect); } } return rect || tmpRect; } } Group.prototype.type = 'group'; // Storage will use childrenRef to get children to render. export interface GroupLike extends Element { childrenRef(): Element[] } export default Group;