You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

300 lines
7.5 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 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;