| /* |
| * 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]: Becuase 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 resposibilities: |
| * (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 then one components, which is convinient is some case). |
| * (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 compoent 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') |
| }; |
| } |
| |
| // // 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; |