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