Spaces:
Running
Running
| /* | |
| * 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 Model from './Model'; | |
| import * as componentUtil from '../util/component'; | |
| import { | |
| enableClassManagement, | |
| parseClassType, | |
| isExtendedClass, | |
| ExtendableConstructor, | |
| ClassManager, | |
| mountExtend | |
| } from '../util/clazz'; | |
| import { | |
| makeInner, ModelFinderIndexQuery, queryReferringComponents, ModelFinderIdQuery, QueryReferringOpt | |
| } from '../util/model'; | |
| import * as layout from '../util/layout'; | |
| import GlobalModel from './Global'; | |
| import { | |
| ComponentOption, | |
| ComponentMainType, | |
| ComponentSubType, | |
| ComponentFullType, | |
| ComponentLayoutMode, | |
| BoxLayoutOptionMixin | |
| } from '../util/types'; | |
| const inner = makeInner<{ | |
| defaultOption: ComponentOption | |
| }, ComponentModel>(); | |
| class ComponentModel<Opt extends ComponentOption = ComponentOption> extends Model<Opt> { | |
| // [Caution]: Because this class or desecendants can be used as `XXX.extend(subProto)`, | |
| // the class members must not be initialized in constructor or declaration place. | |
| // Otherwise there is bad case: | |
| // class A {xxx = 1;} | |
| // enableClassExtend(A); | |
| // class B extends A {} | |
| // var C = B.extend({xxx: 5}); | |
| // var c = new C(); | |
| // console.log(c.xxx); // expect 5 but always 1. | |
| /** | |
| * @readonly | |
| */ | |
| type: ComponentFullType; | |
| /** | |
| * @readonly | |
| */ | |
| id: string; | |
| /** | |
| * Because simplified concept is probably better, series.name (or component.name) | |
| * has been having too many responsibilities: | |
| * (1) Generating id (which requires name in option should not be modified). | |
| * (2) As an index to mapping series when merging option or calling API (a name | |
| * can refer to more than one component, which is convenient is some cases). | |
| * (3) Display. | |
| * @readOnly But injected | |
| */ | |
| name: string; | |
| /** | |
| * @readOnly | |
| */ | |
| mainType: ComponentMainType; | |
| /** | |
| * @readOnly | |
| */ | |
| subType: ComponentSubType; | |
| /** | |
| * @readOnly | |
| */ | |
| componentIndex: number; | |
| /** | |
| * @readOnly | |
| */ | |
| protected defaultOption: ComponentOption; | |
| /** | |
| * @readOnly | |
| */ | |
| ecModel: GlobalModel; | |
| /** | |
| * @readOnly | |
| */ | |
| static dependencies: string[]; | |
| readonly uid: string; | |
| // // No common coordinateSystem needed. Each sub class implement | |
| // // `CoordinateSystemHostModel` itself. | |
| // coordinateSystem: CoordinateSystemMaster | CoordinateSystemExecutive; | |
| /** | |
| * Support merge layout params. | |
| * Only support 'box' now (left/right/top/bottom/width/height). | |
| */ | |
| static layoutMode: ComponentLayoutMode | ComponentLayoutMode['type']; | |
| /** | |
| * Prevent from auto set z, zlevel, z2 by the framework. | |
| */ | |
| preventAutoZ: boolean; | |
| // Injectable properties: | |
| __viewId: string; | |
| __requireNewView: boolean; | |
| static protoInitialize = (function () { | |
| const proto = ComponentModel.prototype; | |
| proto.type = 'component'; | |
| proto.id = ''; | |
| proto.name = ''; | |
| proto.mainType = ''; | |
| proto.subType = ''; | |
| proto.componentIndex = 0; | |
| })(); | |
| constructor(option: Opt, parentModel: Model, ecModel: GlobalModel) { | |
| super(option, parentModel, ecModel); | |
| this.uid = componentUtil.getUID('ec_cpt_model'); | |
| } | |
| init(option: Opt, parentModel: Model, ecModel: GlobalModel): void { | |
| this.mergeDefaultAndTheme(option, ecModel); | |
| } | |
| mergeDefaultAndTheme(option: Opt, ecModel: GlobalModel): void { | |
| const layoutMode = layout.fetchLayoutMode(this); | |
| const inputPositionParams = layoutMode | |
| ? layout.getLayoutParams(option as BoxLayoutOptionMixin) : {}; | |
| const themeModel = ecModel.getTheme(); | |
| zrUtil.merge(option, themeModel.get(this.mainType)); | |
| zrUtil.merge(option, this.getDefaultOption()); | |
| if (layoutMode) { | |
| layout.mergeLayoutParam(option as BoxLayoutOptionMixin, inputPositionParams, layoutMode); | |
| } | |
| } | |
| mergeOption(option: Opt, ecModel: GlobalModel): void { | |
| zrUtil.merge(this.option, option, true); | |
| const layoutMode = layout.fetchLayoutMode(this); | |
| if (layoutMode) { | |
| layout.mergeLayoutParam( | |
| this.option as BoxLayoutOptionMixin, | |
| option as BoxLayoutOptionMixin, | |
| layoutMode | |
| ); | |
| } | |
| } | |
| /** | |
| * Called immediately after `init` or `mergeOption` of this instance called. | |
| */ | |
| optionUpdated(newCptOption: Opt, isInit: boolean): void {} | |
| /** | |
| * [How to declare defaultOption]: | |
| * | |
| * (A) If using class declaration in typescript (since echarts 5): | |
| * ```ts | |
| * import {ComponentOption} from '../model/option'; | |
| * export interface XxxOption extends ComponentOption { | |
| * aaa: number | |
| * } | |
| * export class XxxModel extends Component { | |
| * static type = 'xxx'; | |
| * static defaultOption: XxxOption = { | |
| * aaa: 123 | |
| * } | |
| * } | |
| * Component.registerClass(XxxModel); | |
| * ``` | |
| * ```ts | |
| * import {inheritDefaultOption} from '../util/component'; | |
| * import {XxxModel, XxxOption} from './XxxModel'; | |
| * export interface XxxSubOption extends XxxOption { | |
| * bbb: number | |
| * } | |
| * class XxxSubModel extends XxxModel { | |
| * static defaultOption: XxxSubOption = inheritDefaultOption(XxxModel.defaultOption, { | |
| * bbb: 456 | |
| * }) | |
| * fn() { | |
| * let opt = this.getDefaultOption(); | |
| * // opt is {aaa: 123, bbb: 456} | |
| * } | |
| * } | |
| * ``` | |
| * | |
| * (B) If using class extend (previous approach in echarts 3 & 4): | |
| * ```js | |
| * let XxxComponent = Component.extend({ | |
| * defaultOption: { | |
| * xx: 123 | |
| * } | |
| * }) | |
| * ``` | |
| * ```js | |
| * let XxxSubComponent = XxxComponent.extend({ | |
| * defaultOption: { | |
| * yy: 456 | |
| * }, | |
| * fn: function () { | |
| * let opt = this.getDefaultOption(); | |
| * // opt is {xx: 123, yy: 456} | |
| * } | |
| * }) | |
| * ``` | |
| */ | |
| getDefaultOption(): Opt { | |
| const ctor = this.constructor; | |
| // If using class declaration, it is different to travel super class | |
| // in legacy env and auto merge defaultOption. So if using class | |
| // declaration, defaultOption should be merged manually. | |
| if (!isExtendedClass(ctor)) { | |
| // When using ts class, defaultOption must be declared as static. | |
| return (ctor as any).defaultOption; | |
| } | |
| // FIXME: remove this approach? | |
| const fields = inner(this); | |
| if (!fields.defaultOption) { | |
| const optList = []; | |
| let clz = ctor as ExtendableConstructor; | |
| while (clz) { | |
| const opt = clz.prototype.defaultOption; | |
| opt && optList.push(opt); | |
| clz = clz.superClass; | |
| } | |
| let defaultOption = {}; | |
| for (let i = optList.length - 1; i >= 0; i--) { | |
| defaultOption = zrUtil.merge(defaultOption, optList[i], true); | |
| } | |
| fields.defaultOption = defaultOption; | |
| } | |
| return fields.defaultOption as Opt; | |
| } | |
| /** | |
| * Notice: always force to input param `useDefault` in case that forget to consider it. | |
| * The same behavior as `modelUtil.parseFinder`. | |
| * | |
| * @param useDefault In many cases like series refer axis and axis refer grid, | |
| * If axis index / axis id not specified, use the first target as default. | |
| * In other cases like dataZoom refer axis, if not specified, measn no refer. | |
| */ | |
| getReferringComponents(mainType: ComponentMainType, opt: QueryReferringOpt): { | |
| // Always be array rather than null/undefined, which is convenient to use. | |
| models: ComponentModel[]; | |
| // Whether target component is specified | |
| specified: boolean; | |
| } { | |
| const indexKey = (mainType + 'Index') as keyof Opt; | |
| const idKey = (mainType + 'Id') as keyof Opt; | |
| return queryReferringComponents( | |
| this.ecModel, | |
| mainType, | |
| { | |
| index: this.get(indexKey, true) as unknown as ModelFinderIndexQuery, | |
| id: this.get(idKey, true) as unknown as ModelFinderIdQuery | |
| }, | |
| opt | |
| ); | |
| } | |
| getBoxLayoutParams() { | |
| // Consider itself having box layout configs. | |
| const boxLayoutModel = this as Model<ComponentOption & BoxLayoutOptionMixin>; | |
| return { | |
| left: boxLayoutModel.get('left'), | |
| top: boxLayoutModel.get('top'), | |
| right: boxLayoutModel.get('right'), | |
| bottom: boxLayoutModel.get('bottom'), | |
| width: boxLayoutModel.get('width'), | |
| height: boxLayoutModel.get('height') | |
| }; | |
| } | |
| /** | |
| * Get key for zlevel. | |
| * If developers don't configure zlevel. We will assign zlevel to series based on the key. | |
| * For example, lines with trail effect and progressive series will in an individual zlevel. | |
| */ | |
| getZLevelKey(): string { | |
| return ''; | |
| } | |
| setZLevel(zlevel: number) { | |
| this.option.zlevel = zlevel; | |
| } | |
| // // Interfaces for component / series with select ability. | |
| // select(dataIndex?: number[], dataType?: string): void {} | |
| // unSelect(dataIndex?: number[], dataType?: string): void {} | |
| // getSelectedDataIndices(): number[] { | |
| // return []; | |
| // } | |
| static registerClass: ClassManager['registerClass']; | |
| static hasClass: ClassManager['hasClass']; | |
| static registerSubTypeDefaulter: componentUtil.SubTypeDefaulterManager['registerSubTypeDefaulter']; | |
| } | |
| export type ComponentModelConstructor = typeof ComponentModel | |
| & ClassManager | |
| & componentUtil.SubTypeDefaulterManager | |
| & ExtendableConstructor | |
| & componentUtil.TopologicalTravelable<object>; | |
| mountExtend(ComponentModel, Model); | |
| enableClassManagement(ComponentModel as ComponentModelConstructor); | |
| componentUtil.enableSubTypeDefaulter(ComponentModel as ComponentModelConstructor); | |
| componentUtil.enableTopologicalTravel(ComponentModel as ComponentModelConstructor, getDependencies); | |
| function getDependencies(componentType: string): string[] { | |
| let deps: string[] = []; | |
| zrUtil.each((ComponentModel as ComponentModelConstructor).getClassesByMainType(componentType), function (clz) { | |
| deps = deps.concat((clz as any).dependencies || (clz as any).prototype.dependencies || []); | |
| }); | |
| // Ensure main type. | |
| deps = zrUtil.map(deps, function (type) { | |
| return parseClassType(type).main; | |
| }); | |
| // Hack dataset for convenience. | |
| if (componentType !== 'dataset' && zrUtil.indexOf(deps, 'dataset') <= 0) { | |
| deps.unshift('dataset'); | |
| } | |
| return deps; | |
| } | |
| export default ComponentModel; | |