/
githubmirror
/
echarts
Обзор
Документация
Войти
/
githubmirror
/
echarts
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/core/CoordinateSystem.ts
360 строк
15 KB
100pah
fix&feature:
06 мар 2026, 13:17
06 мар 2026, 13:17
dbfaf6a
Код
Авторство
О чём код?
/* * 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 type GlobalModel from '../model/Global'; import type ExtensionAPI from './ExtensionAPI'; import type { CoordinateSystem, CoordinateSystemCreator, CoordinateSystemMaster } from '../coord/CoordinateSystem'; import { SINGLE_REFERRING } from '../util/model'; import ComponentModel from '../model/Component'; import SeriesModel from '../model/Series'; import { error } from '../util/log'; import { CoordinateSystemDataCoord, NullUndefined } from '../util/types'; type CoordinateSystemCreatorMap = {[type: string]: CoordinateSystemCreator}; /** * FIXME: * `nonSeriesBoxCoordSysCreators` and `_nonSeriesBoxMasterList` are hardcoded implementations. * Regarding "coord sys layout based on another coord sys", currently we only experimentally support one level * dependency, such as, "grid(cartesian)s can be laid out based on matrix/calendar coord sys." * But a comprehensive implementation may need to support: * - Recursive dependencies. e.g., a matrix coord sys lays out based on another matrix coord sys. * That requires in the implementation `create` and `update` of coord sys are called by a dependency graph. * (@see enableTopologicalTravel in `util/component.ts`) */ const nonSeriesBoxCoordSysCreators: CoordinateSystemCreatorMap = {}; const normalCoordSysCreators: CoordinateSystemCreatorMap = {}; class CoordinateSystemManager { private _normalMasterList: CoordinateSystemMaster[] = []; private _nonSeriesBoxMasterList: CoordinateSystemMaster[] = []; /** * Typically, * - in `create`, a coord sys lays out based on a given rect; * - in `update`, update the pixel and data extent of there axes (if any) based on processed `series.data`. * After that, a coord sys can serve (typically by `dataToPoint`/`dataToLayout`/`pointToData`). * If the coordinate system do not lay out based on `series.data`, `update` is not needed. */ create(ecModel: GlobalModel, api: ExtensionAPI): void { this._nonSeriesBoxMasterList = dealCreate(nonSeriesBoxCoordSysCreators, true); this._normalMasterList = dealCreate(normalCoordSysCreators, false); function dealCreate(creatorMap: CoordinateSystemCreatorMap, canBeNonSeriesBox: boolean) { let coordinateSystems: CoordinateSystemMaster[] = []; zrUtil.each(creatorMap, function (creator, type) { const list = creator.create(ecModel, api); coordinateSystems = coordinateSystems.concat(list || []); if (__DEV__) { if (canBeNonSeriesBox) { // Disallow `update` is a brutal way to ensure `_nonSeriesBoxMasterList`s are ready to // serve after `create`. But if `update` has to be involved in `_nonSeriesBoxMasterList` // for some future case, more complicated mechanisms need to be introduced. zrUtil.each(list, master => zrUtil.assert(!master.update)); } } }); return coordinateSystems; } } /** * @see CoordinateSystem['create'] */ update(ecModel: GlobalModel, api: ExtensionAPI): void { zrUtil.each(this._normalMasterList, function (coordSys) { coordSys.update && coordSys.update(ecModel, api); }); } getCoordinateSystems(): CoordinateSystemMaster[] { return this._normalMasterList.concat(this._nonSeriesBoxMasterList); } static register = function (type: string, creator: CoordinateSystemCreator): void { if (type === 'matrix' || type === 'calendar') { // FIXME: hardcode, @see nonSeriesBoxCoordSysCreators nonSeriesBoxCoordSysCreators[type] = creator; return; } normalCoordSysCreators[type] = creator; }; static get = function (type: string): CoordinateSystemCreator { return normalCoordSysCreators[type] || nonSeriesBoxCoordSysCreators[type]; }; } function canBeNonSeriesBoxCoordSys(coordSysType: string): boolean { return !!nonSeriesBoxCoordSysCreators[coordSysType]; } // By default fetch coord from `model.get('coord')`. export const BOX_COORD_SYS_COORD_FROM_PROP_COORD = 1 as const; // Some model/series, such as pie, is allowed to also get coord from `model.get('center')`, // if cannot get from `model.get('coord')`. But historically pie use `center` option, but // geo use `layoutCenter` option to specify layout center; they are not able to be unified. // Therefor it is not recommended. export const BOX_COORD_SYS_COORD_FROM_PROP_COORD2 = 2 as const; export type BoxCoordinateSystemCoordFrom = typeof BOX_COORD_SYS_COORD_FROM_PROP_COORD | typeof BOX_COORD_SYS_COORD_FROM_PROP_COORD2; type BoxCoordinateSystemGetCoord2 = (model: ComponentModel) => CoordinateSystemDataCoord; /** * @see_also `createBoxLayoutReference` * @see_also `injectCoordSysByOption` */ export function registerLayOutOnCoordSysUsage(opt: { // `SomeSeries.type` or `SomeComponent.type` fullType: ComponentModel['type'], // @see BoxCoordinateSystemCoordFrom . Be `false` by default. getCoord2?: BoxCoordinateSystemGetCoord2 }) { if (__DEV__) { zrUtil.assert(!coordSysUseMap.get(opt.fullType)); } coordSysUseMap.set(opt.fullType, {getCoord2: undefined}).getCoord2 = opt.getCoord2; } const coordSysUseMap = zrUtil.createHashMap< {getCoord2: BoxCoordinateSystemGetCoord2 | NullUndefined}, ComponentModel['type'] >(); /** * @return Be an object, but never be NullUndefined. */ export function getCoordForCoordSysUsageKindBox( model: ComponentModel ): { coord: CoordinateSystemDataCoord | NullUndefined from: BoxCoordinateSystemCoordFrom } { let coord: CoordinateSystemDataCoord = model.getShallow('coord', true); let from: BoxCoordinateSystemCoordFrom = BOX_COORD_SYS_COORD_FROM_PROP_COORD; if (coord == null) { const store = coordSysUseMap.get(model.type); if (store && store.getCoord2) { from = BOX_COORD_SYS_COORD_FROM_PROP_COORD2; coord = store.getCoord2(model); } } return {coord, from}; } /** * - `COORD_SYS_USAGE_KIND_DATA`: each data item is laid out based on a coord sys. * - `COORD_SYS_USAGE_KIND_BOX`: the overall bounding rect or anchor point is calculated based on a coord sys. * e.g., * grid rect (cartesian rect) is calculate based on matrix/calendar coord sys; * pie center is calculated based on calendar/cartesian; * * The default value (if not declared in option `coordinateSystemUsage`): * For series, use `COORD_SYS_USAGE_KIND_DATA`, since this is the most common case and backward compatible. * For non-series components, use `COORD_SYS_USAGE_KIND_BOX`, since `COORD_SYS_USAGE_KIND_DATA` is not applicable. */ export const COORD_SYS_USAGE_KIND_NONE = 0 as const; export const COORD_SYS_USAGE_KIND_DATA = 1 as const; export const COORD_SYS_USAGE_KIND_BOX = 2 as const; export type CoordinateSystemUsageKind = typeof COORD_SYS_USAGE_KIND_NONE | typeof COORD_SYS_USAGE_KIND_DATA | typeof COORD_SYS_USAGE_KIND_BOX; export function decideCoordSysUsageKind( // Component or series model: ComponentModel, printError?: boolean ): { kind: CoordinateSystemUsageKind; coordSysType: string | NullUndefined; } { // For backward compat, still not use `true` in model.get. const coordSysType = model.getShallow('coordinateSystem'); let coordSysUsageOption = model.getShallow('coordinateSystemUsage', true); const isDeclaredExplicitly = coordSysUsageOption != null; let kind: CoordinateSystemUsageKind = COORD_SYS_USAGE_KIND_NONE; if (coordSysType) { const isSeries = model.mainType === 'series'; if (coordSysUsageOption == null) { coordSysUsageOption = isSeries ? 'data' : 'box'; } if (coordSysUsageOption === 'data') { kind = COORD_SYS_USAGE_KIND_DATA; if (!isSeries) { if (__DEV__) { if (isDeclaredExplicitly && printError) { error('coordinateSystemUsage "data" is not supported in non-series components.'); } } kind = COORD_SYS_USAGE_KIND_NONE; } } else if (coordSysUsageOption === 'box') { kind = COORD_SYS_USAGE_KIND_BOX; if (!isSeries && !canBeNonSeriesBoxCoordSys(coordSysType)) { if (__DEV__) { if (isDeclaredExplicitly && printError) { error(`coordinateSystem "${coordSysType}" cannot be used` + ` as coordinateSystemUsage "box" for "${model.type}" yet.` ); } } kind = COORD_SYS_USAGE_KIND_NONE; } } } return {coordSysType, kind}; } /** * These cases are considered: * (A) Most series can use only "COORD_SYS_USAGE_KIND_DATA", but "COORD_SYS_USAGE_KIND_BOX" is not applicable: * - e.g., series.heatmap, series.line, series.bar, series.scatter, ... * (B) Some series and most components can use only "COORD_SYS_USAGE_KIND_BOX", but "COORD_SYS_USAGE_KIND_DATA" * is not applicable: * - e.g., series.pie, series.funnel, ... * - e.g., grid, polar, geo, title, ... * (C) Several series can use both "COORD_SYS_USAGE_KIND_BOX" and "COORD_SYS_USAGE_KIND_DATA", even at the same time: * - e.g., series.graph, series.map * - If graph or map series use "COORD_SYS_USAGE_KIND_BOX", it creates a internal coord sys as * "COORD_SYS_USAGE_KIND_DATA" to lay out its data. * - Graph series can use matrix coord sys as either the "COORD_SYS_USAGE_KIND_DATA" (each item layout * on one cell) or "COORD_SYS_USAGE_KIND_BOX" (the entire series are layout within one cell). * - To achieve this effect, * `series.coordinateSystemUsage: 'box'` needs to be specified explicitly. * * Check these echarts option settings: * - If `series: {type: 'bar'}`: * COORD_SYS_USAGE_KIND_DATA: "cartesian2d", * COORD_SYS_USAGE_KIND_BOX: "none". * (since `coordinateSystem: 'cartesian2d'` is the default option in bar.) * - If `grid: {coordinateSystem: 'matrix'}` * COORD_SYS_USAGE_KIND_DATA: "none", * COORD_SYS_USAGE_KIND_BOX: "matrix". * - If `series: {type: 'pie', coordinateSystem: 'matrix'}`: * COORD_SYS_USAGE_KIND_DATA: "none", * COORD_SYS_USAGE_KIND_BOX: "matrix". * (since `coordinateSystemUsage: 'box'` is the default option in pie.) * - If `series: {type: 'graph', coordinateSystem: 'matrix'}`: * COORD_SYS_USAGE_KIND_DATA: "matrix", * COORD_SYS_USAGE_KIND_BOX: "none" * - If `series: {type: 'graph', coordinateSystem: 'matrix', coordinateSystemUsage: 'box'}`: * COORD_SYS_USAGE_KIND_DATA: "an internal view", * COORD_SYS_USAGE_KIND_BOX: "the internal view is laid out on a matrix" * - If `series: {type: 'map'}`: * COORD_SYS_USAGE_KIND_DATA: "a internal geo", * COORD_SYS_USAGE_KIND_BOX: "none" * - If `series: {type: 'map', coordinateSystem: 'geo', geoIndex: 0}`: * COORD_SYS_USAGE_KIND_DATA: "a geo", * COORD_SYS_USAGE_KIND_BOX: "none" * - If `series: {type: 'map', coordinateSystem: 'matrix'}`: * not_applicable * - If `series: {type: 'map', coordinateSystem: 'matrix', coordinateSystemUsage: 'box'}`: * COORD_SYS_USAGE_KIND_DATA: "an internal geo", * COORD_SYS_USAGE_KIND_BOX: "the internal geo is laid out on a matrix" * * @usage * For case (A) & (B), * call `injectCoordSysByOption({coordSysType: 'aaa', ...})` once for each series/components. * For case (C), * call `injectCoordSysByOption({coordSysType: 'aaa', ...})` once for each series/components, * and then call `injectCoordSysByOption({coordSysType: 'bbb', ..., isDefaultDataCoordSys: true})` * once for each series/components. */ export function injectCoordSysByOption(opt: { // series or component targetModel: ComponentModel; coordSysType: string; coordSysProvider: CoordSysInjectionProvider; isDefaultDataCoordSys?: boolean; allowNotFound?: boolean }): CoordinateSystemUsageKind { const { targetModel, coordSysType, coordSysProvider, isDefaultDataCoordSys, allowNotFound, } = opt; if (__DEV__) { zrUtil.assert(!!coordSysType); } let {kind, coordSysType: declaredType} = decideCoordSysUsageKind(targetModel, true); if (isDefaultDataCoordSys && kind !== COORD_SYS_USAGE_KIND_DATA ) { // If both `COORD_SYS_USAGE_KIND_DATA` and `COORD_SYS_USAGE_KIND_BOX` declared in one model. // There is the only case in series-graph, and no other cases yet. kind = COORD_SYS_USAGE_KIND_DATA; declaredType = coordSysType; } if (kind === COORD_SYS_USAGE_KIND_NONE || declaredType !== coordSysType) { return COORD_SYS_USAGE_KIND_NONE; } const coordSys = coordSysProvider(coordSysType, targetModel); if (!coordSys) { if (__DEV__) { if (!allowNotFound) { error(`${coordSysType} cannot be found for` + ` ${targetModel.type} (index: ${targetModel.componentIndex}).` ); } } return COORD_SYS_USAGE_KIND_NONE; } if (kind === COORD_SYS_USAGE_KIND_DATA) { if (__DEV__) { zrUtil.assert(targetModel.mainType === 'series'); } (targetModel as SeriesModel).coordinateSystem = coordSys; } else { // kind === COORD_SYS_USAGE_KIND_BOX targetModel.boxCoordinateSystem = coordSys; } return kind; } type CoordSysInjectionProvider = ( coordSysType: string, injectTargetModel: ComponentModel ) => CoordinateSystem | NullUndefined; export const simpleCoordSysInjectionProvider: CoordSysInjectionProvider = function (coordSysType, injectTargetModel) { const coordSysModel = injectTargetModel.getReferringComponents( coordSysType, SINGLE_REFERRING ).models[0] as (ComponentModel & {coordinateSystem: CoordinateSystem}); return coordSysModel && coordSysModel.coordinateSystem; }; export default CoordinateSystemManager;