You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2021/11/18 04:17:16 UTC
[echarts] branch graphic-animation updated: refact(graphic): seperate view and model
This is an automated email from the ASF dual-hosted git repository.
shenyi pushed a commit to branch graphic-animation
in repository https://gitbox.apache.org/repos/asf/echarts.git
The following commit(s) were added to refs/heads/graphic-animation by this push:
new 3d7bf24 refact(graphic): seperate view and model
3d7bf24 is described below
commit 3d7bf245bfabf2ec2155378b839a7b2573b5bebd
Author: pissang <bm...@gmail.com>
AuthorDate: Thu Nov 18 12:12:47 2021 +0800
refact(graphic): seperate view and model
---
src/component/graphic/GraphicModel.ts | 404 ++++++++++++++++++
src/component/graphic/GraphicView.ts | 352 +++++++++++++++
src/component/graphic/install.ts | 777 +---------------------------------
src/export/option.ts | 2 +-
4 files changed, 780 insertions(+), 755 deletions(-)
diff --git a/src/component/graphic/GraphicModel.ts b/src/component/graphic/GraphicModel.ts
new file mode 100644
index 0000000..e0d8f88
--- /dev/null
+++ b/src/component/graphic/GraphicModel.ts
@@ -0,0 +1,404 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import * as zrUtil from 'zrender/src/core/util';
+import * as modelUtil from '../../util/model';
+import {
+ ComponentOption,
+ BoxLayoutOptionMixin,
+ Dictionary,
+ ZRStyleProps,
+ OptionId,
+ OptionPreprocessor,
+ CommonTooltipOption
+} from '../../util/types';
+import ComponentModel from '../../model/Component';
+import Element, { ElementTextConfig } from 'zrender/src/Element';
+import Displayable from 'zrender/src/graphic/Displayable';
+import { PathProps } from 'zrender/src/graphic/Path';
+import { ImageStyleProps } from 'zrender/src/graphic/Image';
+import GlobalModel from '../../model/Global';
+import { TextStyleProps } from 'zrender/src/graphic/Text';
+import { copyLayoutParams, mergeLayoutParam } from '../../util/layout';
+
+interface GraphicComponentBaseElementOption extends
+ Partial<Pick<
+ Element, TransformProp |
+ 'silent' |
+ 'ignore' |
+ 'draggable' |
+ 'textConfig' |
+ 'onclick' |
+ 'ondblclick' |
+ 'onmouseover' |
+ 'onmouseout' |
+ 'onmousemove' |
+ 'onmousewheel' |
+ 'onmousedown' |
+ 'onmouseup' |
+ 'oncontextmenu' |
+ 'ondrag' |
+ 'ondragstart' |
+ 'ondragend' |
+ 'ondragenter' |
+ 'ondragleave' |
+ 'ondragover' |
+ 'ondrop'
+ >>,
+ /**
+ * left/right/top/bottom: (like 12, '22%', 'center', default undefined)
+ * If left/rigth is set, shape.x/shape.cx/position will not be used.
+ * If top/bottom is set, shape.y/shape.cy/position will not be used.
+ * This mechanism is useful when you want to position a group/element
+ * against the right side or the center of this container.
+ */
+ Partial<Pick<BoxLayoutOptionMixin, 'left' | 'right' | 'top' | 'bottom'>> {
+
+ /**
+ * element type, mandatory.
+ * Only can be omit if call setOption not at the first time and perform merge.
+ */
+ type?: string;
+
+ id?: OptionId;
+ name?: string;
+
+ // Only internal usage. Use specified value does NOT make sense.
+ parentId?: OptionId;
+ parentOption?: GraphicComponentElementOption;
+ children?: GraphicComponentElementOption[];
+ hv?: [boolean, boolean];
+
+ /**
+ * bounding: (enum: 'all' (default) | 'raw')
+ * Specify how to calculate boundingRect when locating.
+ * 'all': Get uioned and transformed boundingRect
+ * from both itself and its descendants.
+ * This mode simplies confining a group of elements in the bounding
+ * of their ancester container (e.g., using 'right: 0').
+ * 'raw': Only use the boundingRect of itself and before transformed.
+ * This mode is similar to css behavior, which is useful when you
+ * want an element to be able to overflow its container. (Consider
+ * a rotated circle needs to be located in a corner.)
+ */
+ bounding?: 'raw' | 'all';
+
+ /**
+ * info: custom info. enables user to mount some info on elements and use them
+ * in event handlers. Update them only when user specified, otherwise, remain.
+ */
+ info?: GraphicExtraElementInfo;
+
+ textContent?: GraphicComponentTextOption;
+ textConfig?: ElementTextConfig;
+
+ $action?: 'merge' | 'replace' | 'remove';
+
+ tooltip?: CommonTooltipOption<unknown>;
+};
+
+
+export type TransformProp = 'x' | 'y' | 'scaleX' | 'scaleY' | 'originX' | 'originY' | 'skewX' | 'skewY' | 'rotation';
+
+export interface GraphicComponentDisplayableOption extends
+ GraphicComponentBaseElementOption, Partial<Pick<Displayable, 'zlevel' | 'z' | 'z2' | 'invisible' | 'cursor'>> {
+
+ style?: ZRStyleProps;
+}
+// TODO: states?
+// interface GraphicComponentDisplayableOptionOnState extends Partial<Pick<
+// Displayable, TransformProp | 'textConfig' | 'z2'
+// >> {
+// style?: ZRStyleProps;
+// }
+export interface GraphicComponentGroupOption extends GraphicComponentBaseElementOption {
+ type?: 'group';
+
+ /**
+ * width/height: (can only be pixel value, default 0)
+ * Only be used to specify contianer(group) size, if needed. And
+ * can not be percentage value (like '33%'). See the reason in the
+ * layout algorithm below.
+ */
+ width?: number;
+ height?: number;
+
+ // TODO: Can only set focus, blur on the root element.
+ // children: Omit<GraphicComponentElementOption, 'focus' | 'blurScope'>[];
+ children: GraphicComponentElementOption[];
+}
+export interface GraphicComponentZRPathOption extends GraphicComponentDisplayableOption {
+ shape?: PathProps['shape'];
+}
+export interface GraphicComponentImageOption extends GraphicComponentDisplayableOption {
+ type?: 'image';
+ style?: ImageStyleProps;
+}
+// TODO: states?
+// interface GraphicComponentImageOptionOnState extends GraphicComponentDisplayableOptionOnState {
+// style?: ImageStyleProps;
+// }
+interface GraphicComponentTextOption
+ extends Omit<GraphicComponentDisplayableOption, 'textContent' | 'textConfig'> {
+ type?: 'text';
+ style?: TextStyleProps;
+}
+export type GraphicComponentElementOption =
+ GraphicComponentGroupOption |
+ GraphicComponentZRPathOption |
+ GraphicComponentImageOption |
+ GraphicComponentTextOption;
+// type GraphicComponentElementOptionOnState =
+// GraphicComponentDisplayableOptionOnState
+// | GraphicComponentImageOptionOnState;
+type GraphicExtraElementInfo = Dictionary<unknown>;
+export type ElementMap = zrUtil.HashMap<Element, string>;
+
+
+export type GraphicComponentLooseOption = (GraphicComponentOption | GraphicComponentElementOption) & {
+ mainType?: 'graphic';
+};
+
+export interface GraphicComponentOption extends ComponentOption {
+ // Note: elements is always behind its ancestors in this elements array.
+ elements?: GraphicComponentElementOption[];
+}
+;
+
+export function setKeyInfoToNewElOption(
+ resultItem: ReturnType<typeof modelUtil.mappingToExists>[number],
+ newElOption: GraphicComponentElementOption
+): void {
+ const existElOption = resultItem.existing as GraphicComponentElementOption;
+
+ // Set id and type after id assigned.
+ newElOption.id = resultItem.keyInfo.id;
+ !newElOption.type && existElOption && (newElOption.type = existElOption.type);
+
+ // Set parent id if not specified
+ if (newElOption.parentId == null) {
+ const newElParentOption = newElOption.parentOption;
+ if (newElParentOption) {
+ newElOption.parentId = newElParentOption.id;
+ }
+ else if (existElOption) {
+ newElOption.parentId = existElOption.parentId;
+ }
+ }
+
+ // Clear
+ newElOption.parentOption = null;
+}
+
+function isSetLoc(
+ obj: GraphicComponentElementOption,
+ props: ('left' | 'right' | 'top' | 'bottom')[]
+): boolean {
+ let isSet;
+ zrUtil.each(props, function (prop) {
+ obj[prop] != null && obj[prop] !== 'auto' && (isSet = true);
+ });
+ return isSet;
+}
+function mergeNewElOptionToExist(
+ existList: GraphicComponentElementOption[],
+ index: number,
+ newElOption: GraphicComponentElementOption
+): void {
+ // Update existing options, for `getOption` feature.
+ const newElOptCopy = zrUtil.extend({}, newElOption);
+ const existElOption = existList[index];
+
+ const $action = newElOption.$action || 'merge';
+ if ($action === 'merge') {
+ if (existElOption) {
+
+ if (__DEV__) {
+ const newType = newElOption.type;
+ zrUtil.assert(
+ !newType || existElOption.type === newType,
+ 'Please set $action: "replace" to change `type`'
+ );
+ }
+
+ // We can ensure that newElOptCopy and existElOption are not
+ // the same object, so `merge` will not change newElOptCopy.
+ zrUtil.merge(existElOption, newElOptCopy, true);
+ // Rigid body, use ignoreSize.
+ mergeLayoutParam(existElOption, newElOptCopy, { ignoreSize: true });
+ // Will be used in render.
+ copyLayoutParams(newElOption, existElOption);
+ }
+ else {
+ existList[index] = newElOptCopy;
+ }
+ }
+ else if ($action === 'replace') {
+ existList[index] = newElOptCopy;
+ }
+ else if ($action === 'remove') {
+ // null will be cleaned later.
+ existElOption && (existList[index] = null);
+ }
+}
+
+function setLayoutInfoToExist(
+ existItem: GraphicComponentElementOption,
+ newElOption: GraphicComponentElementOption
+) {
+ if (!existItem) {
+ return;
+ }
+ existItem.hv = newElOption.hv = [
+ // Rigid body, dont care `width`.
+ isSetLoc(newElOption, ['left', 'right']),
+ // Rigid body, dont care `height`.
+ isSetLoc(newElOption, ['top', 'bottom'])
+ ];
+ // Give default group size. Otherwise layout error may occur.
+ if (existItem.type === 'group') {
+ const existingGroupOpt = existItem as GraphicComponentGroupOption;
+ const newGroupOpt = newElOption as GraphicComponentGroupOption;
+ existingGroupOpt.width == null && (existingGroupOpt.width = newGroupOpt.width = 0);
+ existingGroupOpt.height == null && (existingGroupOpt.height = newGroupOpt.height = 0);
+ }
+}
+
+export class GraphicComponentModel extends ComponentModel<GraphicComponentOption> {
+
+ static type = 'graphic';
+ type = GraphicComponentModel.type;
+
+ preventAutoZ = true;
+
+ static defaultOption: GraphicComponentOption = {
+ elements: []
+ // parentId: null
+ };
+
+ /**
+ * Save el options for the sake of the performance (only update modified graphics).
+ * The order is the same as those in option. (ancesters -> descendants)
+ */
+ private _elOptionsToUpdate: GraphicComponentElementOption[];
+
+ mergeOption(option: GraphicComponentOption, ecModel: GlobalModel): void {
+ // Prevent default merge to elements
+ const elements = this.option.elements;
+ this.option.elements = null;
+
+ super.mergeOption(option, ecModel);
+
+ this.option.elements = elements;
+ }
+
+ optionUpdated(newOption: GraphicComponentOption, isInit: boolean): void {
+ const thisOption = this.option;
+ const newList = (isInit ? thisOption : newOption).elements;
+ const existList = thisOption.elements = isInit ? [] : thisOption.elements;
+
+ const flattenedList = [] as GraphicComponentElementOption[];
+ this._flatten(newList, flattenedList, null);
+
+ const mappingResult = modelUtil.mappingToExists(existList, flattenedList, 'normalMerge');
+
+ // Clear elOptionsToUpdate
+ const elOptionsToUpdate = this._elOptionsToUpdate = [] as GraphicComponentElementOption[];
+
+ zrUtil.each(mappingResult, function (resultItem, index) {
+ const newElOption = resultItem.newOption as GraphicComponentElementOption;
+
+ if (__DEV__) {
+ zrUtil.assert(
+ zrUtil.isObject(newElOption) || resultItem.existing,
+ 'Empty graphic option definition'
+ );
+ }
+
+ if (!newElOption) {
+ return;
+ }
+
+ elOptionsToUpdate.push(newElOption);
+
+ setKeyInfoToNewElOption(resultItem, newElOption);
+
+ mergeNewElOptionToExist(existList, index, newElOption);
+
+ setLayoutInfoToExist(existList[index], newElOption);
+
+ }, this);
+
+ // Clean
+ thisOption.elements = zrUtil.filter(existList, (item) => {
+ // $action should be volatile, otherwise option gotten from
+ // `getOption` will contain unexpected $action.
+ item && delete item.$action;
+ return item != null;
+ });
+ }
+
+ /**
+ * Convert
+ * [{
+ * type: 'group',
+ * id: 'xx',
+ * children: [{type: 'circle'}, {type: 'polygon'}]
+ * }]
+ * to
+ * [
+ * {type: 'group', id: 'xx'},
+ * {type: 'circle', parentId: 'xx'},
+ * {type: 'polygon', parentId: 'xx'}
+ * ]
+ */
+ private _flatten(
+ optionList: GraphicComponentElementOption[],
+ result: GraphicComponentElementOption[],
+ parentOption: GraphicComponentElementOption
+ ): void {
+ zrUtil.each(optionList, function (option) {
+ if (!option) {
+ return;
+ }
+
+ if (parentOption) {
+ option.parentOption = parentOption;
+ }
+
+ result.push(option);
+
+ const children = option.children;
+ if (option.type === 'group' && children) {
+ this._flatten(children, result, option);
+ }
+ // Deleting for JSON output, and for not affecting group creation.
+ delete option.children;
+ }, this);
+ }
+
+ // FIXME
+ // Pass to view using payload? setOption has a payload?
+ useElOptionsToUpdate(): GraphicComponentElementOption[] {
+ const els = this._elOptionsToUpdate;
+ // Clear to avoid render duplicately when zooming.
+ this._elOptionsToUpdate = null;
+ return els;
+ }
+}
diff --git a/src/component/graphic/GraphicView.ts b/src/component/graphic/GraphicView.ts
new file mode 100644
index 0000000..8d3f5c2
--- /dev/null
+++ b/src/component/graphic/GraphicView.ts
@@ -0,0 +1,352 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import * as zrUtil from 'zrender/src/core/util';
+import * as modelUtil from '../../util/model';
+import * as graphicUtil from '../../util/graphic';
+import * as layoutUtil from '../../util/layout';
+import { parsePercent } from '../../util/number';
+import Element from 'zrender/src/Element';
+import GlobalModel from '../../model/Global';
+import ComponentView from '../../view/Component';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import { getECData } from '../../util/innerStore';
+import { TextStyleProps } from 'zrender/src/graphic/Text';
+import { isEC4CompatibleStyle, convertFromEC4CompatibleStyle } from '../../util/styleCompat';
+import {
+ ElementMap,
+ GraphicComponentModel,
+ GraphicComponentDisplayableOption,
+ GraphicComponentZRPathOption,
+ GraphicComponentGroupOption,
+ GraphicComponentElementOption
+} from './GraphicModel';
+
+const _nonShapeGraphicElements = {
+ // Reserved but not supported in graphic component.
+ path: null as unknown,
+ compoundPath: null as unknown,
+
+ // Supported in graphic component.
+ group: graphicUtil.Group,
+ image: graphicUtil.Image,
+ text: graphicUtil.Text
+} as const;
+type NonShapeGraphicElementType = keyof typeof _nonShapeGraphicElements;
+
+export const inner = modelUtil.makeInner<{
+ widthOption: number;
+ heightOption: number;
+ width: number;
+ height: number;
+ id: string;
+}, Element>();
+// ------------------------
+// View
+// ------------------------
+export class GraphicComponentView extends ComponentView {
+
+ static type = 'graphic';
+ type = GraphicComponentView.type;
+
+ private _elMap: ElementMap;
+ private _lastGraphicModel: GraphicComponentModel;
+
+ init() {
+ this._elMap = zrUtil.createHashMap();
+ }
+
+ render(graphicModel: GraphicComponentModel, ecModel: GlobalModel, api: ExtensionAPI): void {
+ // Having leveraged between use cases and algorithm complexity, a very
+ // simple layout mechanism is used:
+ // The size(width/height) can be determined by itself or its parent (not
+ // implemented yet), but can not by its children. (Top-down travel)
+ // The location(x/y) can be determined by the bounding rect of itself
+ // (can including its descendants or not) and the size of its parent.
+ // (Bottom-up travel)
+
+ // When `chart.clear()` or `chart.setOption({...}, true)` with the same id,
+ // view will be reused.
+ if (graphicModel !== this._lastGraphicModel) {
+ this._clear();
+ }
+ this._lastGraphicModel = graphicModel;
+
+ this._updateElements(graphicModel);
+ this._relocate(graphicModel, api);
+ }
+
+ /**
+ * Update graphic elements.
+ */
+ private _updateElements(graphicModel: GraphicComponentModel): void {
+ const elOptionsToUpdate = graphicModel.useElOptionsToUpdate();
+
+ if (!elOptionsToUpdate) {
+ return;
+ }
+
+ const elMap = this._elMap;
+ const rootGroup = this.group;
+
+ // Top-down tranverse to assign graphic settings to each elements.
+ zrUtil.each(elOptionsToUpdate, function (elOption) {
+ const id = modelUtil.convertOptionIdName(elOption.id, null);
+ const elExisting = id != null ? elMap.get(id) : null;
+ const parentId = modelUtil.convertOptionIdName(elOption.parentId, null);
+ const targetElParent = (parentId != null ? elMap.get(parentId) : rootGroup) as graphicUtil.Group;
+
+ const elType = elOption.type;
+ const elOptionStyle = (elOption as GraphicComponentDisplayableOption).style;
+ if (elType === 'text' && elOptionStyle) {
+ // In top/bottom mode, textVerticalAlign should not be used, which cause
+ // inaccurately locating.
+ if (elOption.hv && elOption.hv[1]) {
+ (elOptionStyle as any).textVerticalAlign =
+ (elOptionStyle as any).textBaseline =
+ (elOptionStyle as TextStyleProps).verticalAlign =
+ (elOptionStyle as TextStyleProps).align = null;
+ }
+ }
+
+ let textContentOption = (elOption as GraphicComponentZRPathOption).textContent;
+ let textConfig = (elOption as GraphicComponentZRPathOption).textConfig;
+ if (elOptionStyle
+ && isEC4CompatibleStyle(elOptionStyle, elType, !!textConfig, !!textContentOption)) {
+ const convertResult =
+ convertFromEC4CompatibleStyle(elOptionStyle, elType, true) as GraphicComponentZRPathOption;
+ if (!textConfig && convertResult.textConfig) {
+ textConfig = (elOption as GraphicComponentZRPathOption).textConfig = convertResult.textConfig;
+ }
+ if (!textContentOption && convertResult.textContent) {
+ textContentOption = convertResult.textContent;
+ }
+ }
+
+ // Remove unnecessary props to avoid potential problems.
+ const elOptionCleaned = getCleanedElOption(elOption);
+
+ // For simple, do not support parent change, otherwise reorder is needed.
+ if (__DEV__) {
+ elExisting && zrUtil.assert(
+ targetElParent === elExisting.parent,
+ 'Changing parent is not supported.'
+ );
+ }
+
+ const $action = elOption.$action || 'merge';
+ if ($action === 'merge') {
+ elExisting
+ ? elExisting.attr(elOptionCleaned)
+ : createEl(id, targetElParent, elOptionCleaned, elMap);
+ }
+ else if ($action === 'replace') {
+ removeEl(elExisting, elMap);
+ createEl(id, targetElParent, elOptionCleaned, elMap);
+ }
+ else if ($action === 'remove') {
+ removeEl(elExisting, elMap);
+ }
+
+ const el = elMap.get(id);
+
+ if (el && textContentOption) {
+ if ($action === 'merge') {
+ const textContentExisting = el.getTextContent();
+ textContentExisting
+ ? textContentExisting.attr(textContentOption)
+ : el.setTextContent(new graphicUtil.Text(textContentOption));
+ }
+ else if ($action === 'replace') {
+ el.setTextContent(new graphicUtil.Text(textContentOption));
+ }
+ }
+
+ if (el) {
+ const elInner = inner(el);
+ elInner.widthOption = (elOption as GraphicComponentGroupOption).width;
+ elInner.heightOption = (elOption as GraphicComponentGroupOption).height;
+ setEventData(el, graphicModel, elOption);
+
+ graphicUtil.setTooltipConfig({
+ el: el,
+ componentModel: graphicModel,
+ itemName: el.name,
+ itemTooltipOption: elOption.tooltip
+ });
+ }
+ });
+ }
+
+ /**
+ * Locate graphic elements.
+ */
+ private _relocate(graphicModel: GraphicComponentModel, api: ExtensionAPI): void {
+ const elOptions = graphicModel.option.elements;
+ const rootGroup = this.group;
+ const elMap = this._elMap;
+ const apiWidth = api.getWidth();
+ const apiHeight = api.getHeight();
+
+ // Top-down to calculate percentage width/height of group
+ for (let i = 0; i < elOptions.length; i++) {
+ const elOption = elOptions[i];
+ const id = modelUtil.convertOptionIdName(elOption.id, null);
+ const el = id != null ? elMap.get(id) : null;
+
+ if (!el || !el.isGroup) {
+ continue;
+ }
+ const parentEl = el.parent;
+ const isParentRoot = parentEl === rootGroup;
+ // Like 'position:absolut' in css, default 0.
+ const elInner = inner(el);
+ const parentElInner = inner(parentEl);
+ elInner.width = parsePercent(
+ elInner.widthOption,
+ isParentRoot ? apiWidth : parentElInner.width
+ ) || 0;
+ elInner.height = parsePercent(
+ elInner.heightOption,
+ isParentRoot ? apiHeight : parentElInner.height
+ ) || 0;
+ }
+
+ // Bottom-up tranvese all elements (consider ec resize) to locate elements.
+ for (let i = elOptions.length - 1; i >= 0; i--) {
+ const elOption = elOptions[i];
+ const id = modelUtil.convertOptionIdName(elOption.id, null);
+ const el = id != null ? elMap.get(id) : null;
+
+ if (!el) {
+ continue;
+ }
+
+ const parentEl = el.parent;
+ const parentElInner = inner(parentEl);
+ const containerInfo = parentEl === rootGroup
+ ? {
+ width: apiWidth,
+ height: apiHeight
+ }
+ : {
+ width: parentElInner.width,
+ height: parentElInner.height
+ };
+
+ // PENDING
+ // Currently, when `bounding: 'all'`, the union bounding rect of the group
+ // does not include the rect of [0, 0, group.width, group.height], which
+ // is probably weird for users. Should we make a break change for it?
+ layoutUtil.positionElement(
+ el, elOption, containerInfo, null,
+ { hv: elOption.hv, boundingMode: elOption.bounding }
+ );
+ }
+ }
+
+ /**
+ * Clear all elements.
+ */
+ private _clear(): void {
+ const elMap = this._elMap;
+ elMap.each(function (el) {
+ removeEl(el, elMap);
+ });
+ this._elMap = zrUtil.createHashMap();
+ }
+
+ dispose(): void {
+ this._clear();
+ }
+}
+function createEl(
+ id: string,
+ targetElParent: graphicUtil.Group,
+ elOption: GraphicComponentElementOption,
+ elMap: ElementMap
+): void {
+ const graphicType = elOption.type;
+
+ if (__DEV__) {
+ zrUtil.assert(graphicType, 'graphic type MUST be set');
+ }
+
+ const Clz = (
+ zrUtil.hasOwn(_nonShapeGraphicElements, graphicType)
+ // Those graphic elements are not shapes. They should not be
+ // overwritten by users, so do them first.
+ ? _nonShapeGraphicElements[graphicType as NonShapeGraphicElementType]
+ : graphicUtil.getShapeClass(graphicType)
+ ) as { new(opt: GraphicComponentElementOption): Element; };
+
+ if (__DEV__) {
+ zrUtil.assert(Clz, 'graphic type can not be found');
+ }
+
+ const el = new Clz(elOption);
+ targetElParent.add(el);
+ elMap.set(id, el);
+ inner(el).id = id;
+}
+function removeEl(elExisting: Element, elMap: ElementMap): void {
+ const existElParent = elExisting && elExisting.parent;
+ if (existElParent) {
+ elExisting.type === 'group' && elExisting.traverse(function (el) {
+ removeEl(el, elMap);
+ });
+ elMap.removeKey(inner(elExisting).id);
+ existElParent.remove(elExisting);
+ }
+}
+// Remove unnecessary props to avoid potential problems.
+function getCleanedElOption(
+ elOption: GraphicComponentElementOption
+): Omit<GraphicComponentElementOption, 'textContent'> {
+ elOption = zrUtil.extend({}, elOption);
+ zrUtil.each(
+ ['id', 'parentId', '$action', 'hv', 'bounding', 'textContent'].concat(layoutUtil.LOCATION_PARAMS),
+ function (name) {
+ delete (elOption as any)[name];
+ }
+ );
+ return elOption;
+}
+
+function setEventData(
+ el: Element,
+ graphicModel: GraphicComponentModel,
+ elOption: GraphicComponentElementOption
+): void {
+ let eventData = getECData(el).eventData;
+ // Simple optimize for large amount of elements that no need event.
+ if (!el.silent && !el.ignore && !eventData) {
+ eventData = getECData(el).eventData = {
+ componentType: 'graphic',
+ componentIndex: graphicModel.componentIndex,
+ name: el.name
+ };
+ }
+
+ // `elOption.info` enables user to mount some info on
+ // elements and use them in event handlers.
+ if (eventData) {
+ eventData.info = elOption.info;
+ }
+}
diff --git a/src/component/graphic/install.ts b/src/component/graphic/install.ts
index bfa2a82..4251932 100644
--- a/src/component/graphic/install.ts
+++ b/src/component/graphic/install.ts
@@ -18,769 +18,38 @@
*/
-import * as zrUtil from 'zrender/src/core/util';
-import * as modelUtil from '../../util/model';
-import * as graphicUtil from '../../util/graphic';
-import * as layoutUtil from '../../util/layout';
-import {parsePercent} from '../../util/number';
-import {
- ComponentOption,
- BoxLayoutOptionMixin,
- Dictionary,
- ZRStyleProps,
- OptionId,
- OptionPreprocessor,
- CommonTooltipOption
-} from '../../util/types';
-import ComponentModel from '../../model/Component';
-import Element, { ElementTextConfig } from 'zrender/src/Element';
-import Displayable from 'zrender/src/graphic/Displayable';
-import { PathProps } from 'zrender/src/graphic/Path';
-import { ImageStyleProps } from 'zrender/src/graphic/Image';
-import GlobalModel from '../../model/Global';
-import ComponentView from '../../view/Component';
-import ExtensionAPI from '../../core/ExtensionAPI';
-import { getECData } from '../../util/innerStore';
-import { TextStyleProps } from 'zrender/src/graphic/Text';
-import { isEC4CompatibleStyle, convertFromEC4CompatibleStyle } from '../../util/styleCompat';
+import { isArray } from 'zrender/src/core/util';
import { EChartsExtensionInstallRegisters } from '../../extension';
+import { GraphicComponentModel, GraphicComponentOption } from './GraphicModel';
+import { GraphicComponentView } from './GraphicView';
-type TransformProp = 'x' | 'y' | 'scaleX' | 'scaleY' | 'originX' | 'originY' | 'skewX' | 'skewY' | 'rotation';
-
-interface GraphicComponentBaseElementOption extends
- Partial<Pick<
- Element,
- TransformProp
- | 'silent'
- | 'ignore'
- | 'draggable'
- | 'textConfig'
- | 'onclick'
- | 'ondblclick'
- | 'onmouseover'
- | 'onmouseout'
- | 'onmousemove'
- | 'onmousewheel'
- | 'onmousedown'
- | 'onmouseup'
- | 'oncontextmenu'
- | 'ondrag'
- | 'ondragstart'
- | 'ondragend'
- | 'ondragenter'
- | 'ondragleave'
- | 'ondragover'
- | 'ondrop'
- >>,
- /**
- * left/right/top/bottom: (like 12, '22%', 'center', default undefined)
- * If left/rigth is set, shape.x/shape.cx/position will not be used.
- * If top/bottom is set, shape.y/shape.cy/position will not be used.
- * This mechanism is useful when you want to position a group/element
- * against the right side or the center of this container.
- */
- Partial<Pick<BoxLayoutOptionMixin, 'left' | 'right' | 'top' | 'bottom'>> {
-
- /**
- * element type, mandatory.
- * Only can be omit if call setOption not at the first time and perform merge.
- */
- type?: string;
-
- id?: OptionId;
- name?: string;
-
- // Only internal usage. Use specified value does NOT make sense.
- parentId?: OptionId;
- parentOption?: GraphicComponentElementOption;
- children?: GraphicComponentElementOption[];
- hv?: [boolean, boolean];
-
- /**
- * bounding: (enum: 'all' (default) | 'raw')
- * Specify how to calculate boundingRect when locating.
- * 'all': Get uioned and transformed boundingRect
- * from both itself and its descendants.
- * This mode simplies confining a group of elements in the bounding
- * of their ancester container (e.g., using 'right: 0').
- * 'raw': Only use the boundingRect of itself and before transformed.
- * This mode is similar to css behavior, which is useful when you
- * want an element to be able to overflow its container. (Consider
- * a rotated circle needs to be located in a corner.)
- */
- bounding?: 'raw' | 'all';
-
- /**
- * info: custom info. enables user to mount some info on elements and use them
- * in event handlers. Update them only when user specified, otherwise, remain.
- */
- info?: GraphicExtraElementInfo;
-
- textContent?: GraphicComponentTextOption;
- textConfig?: ElementTextConfig;
-
- $action?: 'merge' | 'replace' | 'remove';
-
- tooltip?: CommonTooltipOption<unknown>;
-};
-
-interface GraphicComponentDisplayableOption extends
- GraphicComponentBaseElementOption,
- Partial<Pick<Displayable, 'zlevel' | 'z' | 'z2' | 'invisible' | 'cursor'>> {
-
- style?: ZRStyleProps;
-
- // TODO: states?
- // emphasis?: GraphicComponentDisplayableOptionOnState;
- // blur?: GraphicComponentDisplayableOptionOnState;
- // select?: GraphicComponentDisplayableOptionOnState;
-}
-// TODO: states?
-// interface GraphicComponentDisplayableOptionOnState extends Partial<Pick<
-// Displayable, TransformProp | 'textConfig' | 'z2'
-// >> {
-// style?: ZRStyleProps;
-// }
-interface GraphicComponentGroupOption extends GraphicComponentBaseElementOption {
- type?: 'group';
-
- /**
- * width/height: (can only be pixel value, default 0)
- * Only be used to specify contianer(group) size, if needed. And
- * can not be percentage value (like '33%'). See the reason in the
- * layout algorithm below.
- */
- width?: number;
- height?: number;
-
- // TODO: Can only set focus, blur on the root element.
- // children: Omit<GraphicComponentElementOption, 'focus' | 'blurScope'>[];
- children: GraphicComponentElementOption[];
-}
-export interface GraphicComponentZRPathOption extends GraphicComponentDisplayableOption {
- shape?: PathProps['shape'];
-}
-export interface GraphicComponentImageOption extends GraphicComponentDisplayableOption {
- type?: 'image';
- style?: ImageStyleProps;
- // TODO: states?
- // emphasis?: GraphicComponentImageOptionOnState;
- // blur?: GraphicComponentImageOptionOnState;
- // select?: GraphicComponentImageOptionOnState;
-}
-// TODO: states?
-// interface GraphicComponentImageOptionOnState extends GraphicComponentDisplayableOptionOnState {
-// style?: ImageStyleProps;
-// }
-interface GraphicComponentTextOption
- extends Omit<GraphicComponentDisplayableOption, 'textContent' | 'textConfig'> {
- type?: 'text';
- style?: TextStyleProps;
-}
-type GraphicComponentElementOption =
- GraphicComponentGroupOption
- | GraphicComponentZRPathOption
- | GraphicComponentImageOption
- | GraphicComponentTextOption;
-// type GraphicComponentElementOptionOnState =
-// GraphicComponentDisplayableOptionOnState
-// | GraphicComponentImageOptionOnState;
-
-type GraphicExtraElementInfo = Dictionary<unknown>;
-
-type ElementMap = zrUtil.HashMap<Element, string>;
-
-const inner = modelUtil.makeInner<{
- widthOption: number;
- heightOption: number;
- width: number;
- height: number;
- id: string;
-}, Element>();
-
-
-const _nonShapeGraphicElements = {
-
- // Reserved but not supported in graphic component.
- path: null as unknown,
- compoundPath: null as unknown,
-
- // Supported in graphic component.
- group: graphicUtil.Group,
- image: graphicUtil.Image,
- text: graphicUtil.Text
-} as const;
-type NonShapeGraphicElementType = keyof typeof _nonShapeGraphicElements;
-
-// ------------------------
-// Preprocessor
-// ------------------------
-
-const preprocessor: OptionPreprocessor = function (option) {
- const graphicOption = option.graphic as GraphicComponentOption | GraphicComponentOption[];
-
- // Convert
- // {graphic: [{left: 10, type: 'circle'}, ...]}
- // or
- // {graphic: {left: 10, type: 'circle'}}
- // to
- // {graphic: [{elements: [{left: 10, type: 'circle'}, ...]}]}
- if (zrUtil.isArray(graphicOption)) {
- if (!graphicOption[0] || !graphicOption[0].elements) {
- option.graphic = [{elements: graphicOption}];
- }
- else {
- // Only one graphic instance can be instantiated. (We dont
- // want that too many views are created in echarts._viewMap)
- option.graphic = [(option.graphic as any)[0]];
- }
- }
- else if (graphicOption && !graphicOption.elements) {
- option.graphic = [{elements: [graphicOption]}];
- }
-};
-
-// ------------------------
-// Model
-// ------------------------
-
-export type GraphicComponentLooseOption = (GraphicComponentOption | GraphicComponentElementOption) & {
- mainType?: 'graphic';
-};
-
-export interface GraphicComponentOption extends ComponentOption {
- // Note: elements is always behind its ancestors in this elements array.
- elements?: GraphicComponentElementOption[];
- // parentId: string;
-};
-
-
-class GraphicComponentModel extends ComponentModel<GraphicComponentOption> {
-
- static type = 'graphic';
- type = GraphicComponentModel.type;
-
- preventAutoZ = true;
-
- static defaultOption: GraphicComponentOption = {
- elements: []
- // parentId: null
- };
-
- /**
- * Save el options for the sake of the performance (only update modified graphics).
- * The order is the same as those in option. (ancesters -> descendants)
- */
- private _elOptionsToUpdate: GraphicComponentElementOption[];
-
- mergeOption(option: GraphicComponentOption, ecModel: GlobalModel): void {
- // Prevent default merge to elements
- const elements = this.option.elements;
- this.option.elements = null;
-
- super.mergeOption(option, ecModel);
-
- this.option.elements = elements;
- }
-
- optionUpdated(newOption: GraphicComponentOption, isInit: boolean): void {
- const thisOption = this.option;
- const newList = (isInit ? thisOption : newOption).elements;
- const existList = thisOption.elements = isInit ? [] : thisOption.elements;
-
- const flattenedList = [] as GraphicComponentElementOption[];
- this._flatten(newList, flattenedList, null);
-
- const mappingResult = modelUtil.mappingToExists(existList, flattenedList, 'normalMerge');
-
- // Clear elOptionsToUpdate
- const elOptionsToUpdate = this._elOptionsToUpdate = [] as GraphicComponentElementOption[];
-
- zrUtil.each(mappingResult, function (resultItem, index) {
- const newElOption = resultItem.newOption as GraphicComponentElementOption;
-
- if (__DEV__) {
- zrUtil.assert(
- zrUtil.isObject(newElOption) || resultItem.existing,
- 'Empty graphic option definition'
- );
- }
-
- if (!newElOption) {
- return;
- }
-
- elOptionsToUpdate.push(newElOption);
-
- setKeyInfoToNewElOption(resultItem, newElOption);
-
- mergeNewElOptionToExist(existList, index, newElOption);
-
- setLayoutInfoToExist(existList[index], newElOption);
-
- }, this);
-
- // Clean
- thisOption.elements = zrUtil.filter(existList, (item) => {
- // $action should be volatile, otherwise option gotten from
- // `getOption` will contain unexpected $action.
- item && delete item.$action;
- return item != null;
- });
- }
-
- /**
- * Convert
- * [{
- * type: 'group',
- * id: 'xx',
- * children: [{type: 'circle'}, {type: 'polygon'}]
- * }]
- * to
- * [
- * {type: 'group', id: 'xx'},
- * {type: 'circle', parentId: 'xx'},
- * {type: 'polygon', parentId: 'xx'}
- * ]
- */
- private _flatten(
- optionList: GraphicComponentElementOption[],
- result: GraphicComponentElementOption[],
- parentOption: GraphicComponentElementOption
- ): void {
- zrUtil.each(optionList, function (option) {
- if (!option) {
- return;
- }
-
- if (parentOption) {
- option.parentOption = parentOption;
- }
-
- result.push(option);
-
- const children = option.children;
- if (option.type === 'group' && children) {
- this._flatten(children, result, option);
- }
- // Deleting for JSON output, and for not affecting group creation.
- delete option.children;
- }, this);
- }
-
- // FIXME
- // Pass to view using payload? setOption has a payload?
- useElOptionsToUpdate(): GraphicComponentElementOption[] {
- const els = this._elOptionsToUpdate;
- // Clear to avoid render duplicately when zooming.
- this._elOptionsToUpdate = null;
- return els;
- }
-}
-
-// ------------------------
-// View
-// ------------------------
-
-class GraphicComponentView extends ComponentView {
-
- static type = 'graphic';
- type = GraphicComponentView.type;
-
- private _elMap: ElementMap;
- private _lastGraphicModel: GraphicComponentModel;
-
- init() {
- this._elMap = zrUtil.createHashMap();
- }
-
- render(graphicModel: GraphicComponentModel, ecModel: GlobalModel, api: ExtensionAPI): void {
-
- // Having leveraged between use cases and algorithm complexity, a very
- // simple layout mechanism is used:
- // The size(width/height) can be determined by itself or its parent (not
- // implemented yet), but can not by its children. (Top-down travel)
- // The location(x/y) can be determined by the bounding rect of itself
- // (can including its descendants or not) and the size of its parent.
- // (Bottom-up travel)
-
- // When `chart.clear()` or `chart.setOption({...}, true)` with the same id,
- // view will be reused.
- if (graphicModel !== this._lastGraphicModel) {
- this._clear();
- }
- this._lastGraphicModel = graphicModel;
-
- this._updateElements(graphicModel);
- this._relocate(graphicModel, api);
- }
-
- /**
- * Update graphic elements.
- */
- private _updateElements(graphicModel: GraphicComponentModel): void {
- const elOptionsToUpdate = graphicModel.useElOptionsToUpdate();
-
- if (!elOptionsToUpdate) {
- return;
- }
-
- const elMap = this._elMap;
- const rootGroup = this.group;
-
- // Top-down tranverse to assign graphic settings to each elements.
- zrUtil.each(elOptionsToUpdate, function (elOption) {
- const id = modelUtil.convertOptionIdName(elOption.id, null);
- const elExisting = id != null ? elMap.get(id) : null;
- const parentId = modelUtil.convertOptionIdName(elOption.parentId, null);
- const targetElParent = (parentId != null ? elMap.get(parentId) : rootGroup) as graphicUtil.Group;
-
- const elType = elOption.type;
- const elOptionStyle = (elOption as GraphicComponentDisplayableOption).style;
- if (elType === 'text' && elOptionStyle) {
- // In top/bottom mode, textVerticalAlign should not be used, which cause
- // inaccurately locating.
- if (elOption.hv && elOption.hv[1]) {
- (elOptionStyle as any).textVerticalAlign =
- (elOptionStyle as any).textBaseline =
- (elOptionStyle as TextStyleProps).verticalAlign =
- (elOptionStyle as TextStyleProps).align = null;
- }
- }
-
- let textContentOption = (elOption as GraphicComponentZRPathOption).textContent;
- let textConfig = (elOption as GraphicComponentZRPathOption).textConfig;
- if (elOptionStyle
- && isEC4CompatibleStyle(elOptionStyle, elType, !!textConfig, !!textContentOption)
- ) {
- const convertResult =
- convertFromEC4CompatibleStyle(elOptionStyle, elType, true) as GraphicComponentZRPathOption;
- if (!textConfig && convertResult.textConfig) {
- textConfig = (elOption as GraphicComponentZRPathOption).textConfig = convertResult.textConfig;
- }
- if (!textContentOption && convertResult.textContent) {
- textContentOption = convertResult.textContent;
- }
- }
-
- // Remove unnecessary props to avoid potential problems.
- const elOptionCleaned = getCleanedElOption(elOption);
-
- // For simple, do not support parent change, otherwise reorder is needed.
- if (__DEV__) {
- elExisting && zrUtil.assert(
- targetElParent === elExisting.parent,
- 'Changing parent is not supported.'
- );
- }
-
- const $action = elOption.$action || 'merge';
- if ($action === 'merge') {
- elExisting
- ? elExisting.attr(elOptionCleaned)
- : createEl(id, targetElParent, elOptionCleaned, elMap);
- }
- else if ($action === 'replace') {
- removeEl(elExisting, elMap);
- createEl(id, targetElParent, elOptionCleaned, elMap);
- }
- else if ($action === 'remove') {
- removeEl(elExisting, elMap);
- }
-
- const el = elMap.get(id);
+export function install(registers: EChartsExtensionInstallRegisters) {
- if (el && textContentOption) {
- if ($action === 'merge') {
- const textContentExisting = el.getTextContent();
- textContentExisting
- ? textContentExisting.attr(textContentOption)
- : el.setTextContent(new graphicUtil.Text(textContentOption));
- }
- else if ($action === 'replace') {
- el.setTextContent(new graphicUtil.Text(textContentOption));
- }
- }
+ registers.registerComponentModel(GraphicComponentModel);
+ registers.registerComponentView(GraphicComponentView);
- if (el) {
- const elInner = inner(el);
- elInner.widthOption = (elOption as GraphicComponentGroupOption).width;
- elInner.heightOption = (elOption as GraphicComponentGroupOption).height;
- setEventData(el, graphicModel, elOption);
+ registers.registerPreprocessor(function (option) {
+ const graphicOption = option.graphic as GraphicComponentOption | GraphicComponentOption[];
- graphicUtil.setTooltipConfig({
- el: el,
- componentModel: graphicModel,
- itemName: el.name,
- itemTooltipOption: elOption.tooltip
- });
+ // Convert
+ // {graphic: [{left: 10, type: 'circle'}, ...]}
+ // or
+ // {graphic: {left: 10, type: 'circle'}}
+ // to
+ // {graphic: [{elements: [{left: 10, type: 'circle'}, ...]}]}
+ if (isArray(graphicOption)) {
+ if (!graphicOption[0] || !graphicOption[0].elements) {
+ option.graphic = [{ elements: graphicOption }];
}
- });
- }
-
- /**
- * Locate graphic elements.
- */
- private _relocate(graphicModel: GraphicComponentModel, api: ExtensionAPI): void {
- const elOptions = graphicModel.option.elements;
- const rootGroup = this.group;
- const elMap = this._elMap;
- const apiWidth = api.getWidth();
- const apiHeight = api.getHeight();
-
- // Top-down to calculate percentage width/height of group
- for (let i = 0; i < elOptions.length; i++) {
- const elOption = elOptions[i];
- const id = modelUtil.convertOptionIdName(elOption.id, null);
- const el = id != null ? elMap.get(id) : null;
-
- if (!el || !el.isGroup) {
- continue;
+ else {
+ // Only one graphic instance can be instantiated. (We dont
+ // want that too many views are created in echarts._viewMap)
+ option.graphic = [(option.graphic as any)[0]];
}
- const parentEl = el.parent;
- const isParentRoot = parentEl === rootGroup;
- // Like 'position:absolut' in css, default 0.
- const elInner = inner(el);
- const parentElInner = inner(parentEl);
- elInner.width = parsePercent(
- elInner.widthOption,
- isParentRoot ? apiWidth : parentElInner.width
- ) || 0;
- elInner.height = parsePercent(
- elInner.heightOption,
- isParentRoot ? apiHeight : parentElInner.height
- ) || 0;
}
-
- // Bottom-up tranvese all elements (consider ec resize) to locate elements.
- for (let i = elOptions.length - 1; i >= 0; i--) {
- const elOption = elOptions[i];
- const id = modelUtil.convertOptionIdName(elOption.id, null);
- const el = id != null ? elMap.get(id) : null;
-
- if (!el) {
- continue;
- }
-
- const parentEl = el.parent;
- const parentElInner = inner(parentEl);
- const containerInfo = parentEl === rootGroup
- ? {
- width: apiWidth,
- height: apiHeight
- }
- : {
- width: parentElInner.width,
- height: parentElInner.height
- };
-
- // PENDING
- // Currently, when `bounding: 'all'`, the union bounding rect of the group
- // does not include the rect of [0, 0, group.width, group.height], which
- // is probably weird for users. Should we make a break change for it?
- layoutUtil.positionElement(
- el, elOption, containerInfo, null,
- {hv: elOption.hv, boundingMode: elOption.bounding}
- );
- }
- }
-
- /**
- * Clear all elements.
- */
- private _clear(): void {
- const elMap = this._elMap;
- elMap.each(function (el) {
- removeEl(el, elMap);
- });
- this._elMap = zrUtil.createHashMap();
- }
-
- dispose(): void {
- this._clear();
- }
-}
-
-function createEl(
- id: string,
- targetElParent: graphicUtil.Group,
- elOption: GraphicComponentElementOption,
- elMap: ElementMap
-): void {
- const graphicType = elOption.type;
-
- if (__DEV__) {
- zrUtil.assert(graphicType, 'graphic type MUST be set');
- }
-
- const Clz = (
- zrUtil.hasOwn(_nonShapeGraphicElements, graphicType)
- // Those graphic elements are not shapes. They should not be
- // overwritten by users, so do them first.
- ? _nonShapeGraphicElements[graphicType as NonShapeGraphicElementType]
- : graphicUtil.getShapeClass(graphicType)
- ) as { new(opt: GraphicComponentElementOption): Element };
-
- if (__DEV__) {
- zrUtil.assert(Clz, 'graphic type can not be found');
- }
-
- const el = new Clz(elOption);
- targetElParent.add(el);
- elMap.set(id, el);
- inner(el).id = id;
-}
-
-function removeEl(elExisting: Element, elMap: ElementMap): void {
- const existElParent = elExisting && elExisting.parent;
- if (existElParent) {
- elExisting.type === 'group' && elExisting.traverse(function (el) {
- removeEl(el, elMap);
- });
- elMap.removeKey(inner(elExisting).id);
- existElParent.remove(elExisting);
- }
-}
-
-// Remove unnecessary props to avoid potential problems.
-function getCleanedElOption(
- elOption: GraphicComponentElementOption
-): Omit<GraphicComponentElementOption, 'textContent'> {
- elOption = zrUtil.extend({}, elOption);
- zrUtil.each(
- ['id', 'parentId', '$action', 'hv', 'bounding', 'textContent'].concat(layoutUtil.LOCATION_PARAMS),
- function (name) {
- delete (elOption as any)[name];
+ else if (graphicOption && !graphicOption.elements) {
+ option.graphic = [{ elements: [graphicOption] }];
}
- );
- return elOption;
-}
-
-function isSetLoc(
- obj: GraphicComponentElementOption,
- props: ('left' | 'right' | 'top' | 'bottom')[]
-): boolean {
- let isSet;
- zrUtil.each(props, function (prop) {
- obj[prop] != null && obj[prop] !== 'auto' && (isSet = true);
});
- return isSet;
-}
-
-function setKeyInfoToNewElOption(
- resultItem: ReturnType<typeof modelUtil.mappingToExists>[number],
- newElOption: GraphicComponentElementOption
-): void {
- const existElOption = resultItem.existing as GraphicComponentElementOption;
-
- // Set id and type after id assigned.
- newElOption.id = resultItem.keyInfo.id;
- !newElOption.type && existElOption && (newElOption.type = existElOption.type);
-
- // Set parent id if not specified
- if (newElOption.parentId == null) {
- const newElParentOption = newElOption.parentOption;
- if (newElParentOption) {
- newElOption.parentId = newElParentOption.id;
- }
- else if (existElOption) {
- newElOption.parentId = existElOption.parentId;
- }
- }
-
- // Clear
- newElOption.parentOption = null;
-}
-
-function mergeNewElOptionToExist(
- existList: GraphicComponentElementOption[],
- index: number,
- newElOption: GraphicComponentElementOption
-): void {
- // Update existing options, for `getOption` feature.
- const newElOptCopy = zrUtil.extend({}, newElOption);
- const existElOption = existList[index];
-
- const $action = newElOption.$action || 'merge';
- if ($action === 'merge') {
- if (existElOption) {
-
- if (__DEV__) {
- const newType = newElOption.type;
- zrUtil.assert(
- !newType || existElOption.type === newType,
- 'Please set $action: "replace" to change `type`'
- );
- }
-
- // We can ensure that newElOptCopy and existElOption are not
- // the same object, so `merge` will not change newElOptCopy.
- zrUtil.merge(existElOption, newElOptCopy, true);
- // Rigid body, use ignoreSize.
- layoutUtil.mergeLayoutParam(existElOption, newElOptCopy, {ignoreSize: true});
- // Will be used in render.
- layoutUtil.copyLayoutParams(newElOption, existElOption);
- }
- else {
- existList[index] = newElOptCopy;
- }
- }
- else if ($action === 'replace') {
- existList[index] = newElOptCopy;
- }
- else if ($action === 'remove') {
- // null will be cleaned later.
- existElOption && (existList[index] = null);
- }
-}
-
-function setLayoutInfoToExist(
- existItem: GraphicComponentElementOption,
- newElOption: GraphicComponentElementOption
-) {
- if (!existItem) {
- return;
- }
- existItem.hv = newElOption.hv = [
- // Rigid body, dont care `width`.
- isSetLoc(newElOption, ['left', 'right']),
- // Rigid body, dont care `height`.
- isSetLoc(newElOption, ['top', 'bottom'])
- ];
- // Give default group size. Otherwise layout error may occur.
- if (existItem.type === 'group') {
- const existingGroupOpt = existItem as GraphicComponentGroupOption;
- const newGroupOpt = newElOption as GraphicComponentGroupOption;
- existingGroupOpt.width == null && (existingGroupOpt.width = newGroupOpt.width = 0);
- existingGroupOpt.height == null && (existingGroupOpt.height = newGroupOpt.height = 0);
- }
-}
-
-function setEventData(
- el: Element,
- graphicModel: GraphicComponentModel,
- elOption: GraphicComponentElementOption
-): void {
- let eventData = getECData(el).eventData;
- // Simple optimize for large amount of elements that no need event.
- if (!el.silent && !el.ignore && !eventData) {
- eventData = getECData(el).eventData = {
- componentType: 'graphic',
- componentIndex: graphicModel.componentIndex,
- name: el.name
- };
- }
-
- // `elOption.info` enables user to mount some info on
- // elements and use them in event handlers.
- if (eventData) {
- eventData.info = elOption.info;
- }
-}
-
-export function install(registers: EChartsExtensionInstallRegisters) {
- registers.registerComponentModel(GraphicComponentModel);
- registers.registerComponentView(GraphicComponentView);
- registers.registerPreprocessor(preprocessor);
}
\ No newline at end of file
diff --git a/src/export/option.ts b/src/export/option.ts
index dff46af..d25e819 100644
--- a/src/export/option.ts
+++ b/src/export/option.ts
@@ -94,7 +94,7 @@ import type {
CustomSeriesRenderItem
} from '../chart/custom/CustomSeries';
-import type { GraphicComponentLooseOption as GraphicComponentOption } from '../component/graphic/install';
+import { GraphicComponentLooseOption as GraphicComponentOption } from '../component/graphic/GraphicModel';
import type { DatasetOption as DatasetComponentOption } from '../component/dataset/install';
import type {ToolboxBrushFeatureOption} from '../component/toolbox/feature/Brush';
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org