This is an automated email from the ASF dual-hosted git repository. sushuang pushed a commit to branch PR/plainheart_fix/alignTicks-precision in repository https://gitbox.apache.org/repos/asf/echarts.git
commit bd19110f247d9221849f43d18f8b49b590095520 Author: 100pah <[email protected]> AuthorDate: Sun Mar 8 02:51:11 2026 +0800 fix: (1) Clarify and uniform terminology and add comments to explain EC_CYCLE. (2) Fix dataZoom in `appendData`, introduced by recent commits. --- src/component/dataZoom/dataZoomProcessor.ts | 2 + src/component/dataZoom/helper.ts | 3 +- src/component/thumbnail/ThumbnailBridgeImpl.ts | 2 +- src/component/thumbnail/ThumbnailView.ts | 2 +- src/coord/axisAlignTicks.ts | 4 + src/coord/axisStatistics.ts | 19 ++-- src/coord/scaleRawExtentInfo.ts | 12 +- src/core/ExtensionAPI.ts | 2 +- src/core/Scheduler.ts | 23 ++-- src/core/echarts.ts | 150 +++++++++++++++++-------- src/core/task.ts | 10 +- src/label/LabelManager.ts | 2 +- src/label/labelLayoutHelper.ts | 2 +- src/model/OptionManager.ts | 6 - src/scale/scaleMapper.ts | 4 +- src/util/cycleCache.ts | 73 ++++++++++++ src/util/model.ts | 50 --------- src/util/types.ts | 57 ++++++++-- test/bar-polar-multi-series-radial.html | 2 +- 19 files changed, 269 insertions(+), 156 deletions(-) diff --git a/src/component/dataZoom/dataZoomProcessor.ts b/src/component/dataZoom/dataZoomProcessor.ts index 96033e38d..811b6a264 100644 --- a/src/component/dataZoom/dataZoomProcessor.ts +++ b/src/component/dataZoom/dataZoomProcessor.ts @@ -30,6 +30,8 @@ import { AxisBaseModel } from '../../coord/AxisBaseModel'; const dataZoomProcessor: StageHandler = { + dirtyOnOverallProgress: true, + // `dataZoomProcessor` will only be performed in needed series. Consider if // there is a line series and a pie series, it is better not to update the // line series if only pie series is needed to be updated. diff --git a/src/component/dataZoom/helper.ts b/src/component/dataZoom/helper.ts index d3170ea2b..9fb7c3b58 100644 --- a/src/component/dataZoom/helper.ts +++ b/src/component/dataZoom/helper.ts @@ -25,8 +25,9 @@ import SeriesModel from '../../model/Series'; import { CoordinateSystemHostModel } from '../../coord/CoordinateSystem'; import { AxisBaseModel } from '../../coord/AxisBaseModel'; import type AxisProxy from './AxisProxy'; -import { getCachePerECPrepare, GlobalModelCachePerECPrepare, makeInner } from '../../util/model'; +import { makeInner } from '../../util/model'; import type ComponentModel from '../../model/Component'; +import { getCachePerECPrepare, GlobalModelCachePerECPrepare } from '../../util/cycleCache'; export interface DataZoomPayloadBatchItem { diff --git a/src/component/thumbnail/ThumbnailBridgeImpl.ts b/src/component/thumbnail/ThumbnailBridgeImpl.ts index 9b33cec8c..f155613dc 100644 --- a/src/component/thumbnail/ThumbnailBridgeImpl.ts +++ b/src/component/thumbnail/ThumbnailBridgeImpl.ts @@ -51,7 +51,7 @@ export class ThumbnailBridgeImpl implements ThumbnailBridge { } reset(api: ExtensionAPI) { - this._renderVersion = api.getMainProcessVersion(); + this._renderVersion = api.getECMainCycleVersion(); } renderContent(opt: { diff --git a/src/component/thumbnail/ThumbnailView.ts b/src/component/thumbnail/ThumbnailView.ts index 617f6dfda..a08f1470b 100644 --- a/src/component/thumbnail/ThumbnailView.ts +++ b/src/component/thumbnail/ThumbnailView.ts @@ -70,7 +70,7 @@ export class ThumbnailView extends ComponentView { return; } - this._renderVersion = api.getMainProcessVersion(); + this._renderVersion = api.getECMainCycleVersion(); const group = this.group; group.removeAll(); diff --git a/src/coord/axisAlignTicks.ts b/src/coord/axisAlignTicks.ts index 0fb54eedd..61f11ed20 100644 --- a/src/coord/axisAlignTicks.ts +++ b/src/coord/axisAlignTicks.ts @@ -56,6 +56,10 @@ export function scaleCalcAlign( targetScale, targetAxisModel, targetAxisModel.ecModel, targetAxis, null ); + // FIXME: + // (1) Axis inverse is not considered yet. + // (2) `SCALE_EXTENT_KIND_MAPPING` is not considered yet. + const isTargetLogScale = isLogScale(targetScale); const alignToScaleLinear = isLogScale(alignToScale) ? alignToScale.intervalStub : alignToScale; const targetIntervalStub = isTargetLogScale ? targetScale.intervalStub : targetScale; diff --git a/src/coord/axisStatistics.ts b/src/coord/axisStatistics.ts index f64ae1faa..ca2454619 100644 --- a/src/coord/axisStatistics.ts +++ b/src/coord/axisStatistics.ts @@ -21,8 +21,6 @@ import { assert, createHashMap, each, HashMap } from 'zrender/src/core/util'; import type GlobalModel from '../model/Global'; import type SeriesModel from '../model/Series'; import { - getCachePerECFullUpdate, getCachePerECPrepare, GlobalModelCachePerECFullUpdate, - GlobalModelCachePerECPrepare, initExtentForUnion, makeCallOnlyOnce, makeInner, } from '../util/model'; import { DimensionIndex, NullUndefined } from '../util/types'; @@ -34,6 +32,10 @@ import type { AxisBaseModel } from './AxisBaseModel'; import { tryEnsureTypedArray, Float64ArrayCtor } from '../util/vendor'; import { EChartsExtensionInstallRegisters } from '../extension'; import type ComponentModel from '../model/Component'; +import { + getCachePerECFullUpdate, getCachePerECPrepare, GlobalModelCachePerECFullUpdate, + GlobalModelCachePerECPrepare +} from '../util/cycleCache'; const callOnlyOnce = makeCallOnlyOnce(); @@ -237,7 +239,7 @@ export function eachAxisStatKey( }); } -function performAxisStatistics(ecModel: GlobalModel): void { +function performAxisStatisticsOnOverallReset(ecModel: GlobalModel): void { const ecFullUpdateCache = ecModelCacheFullUpdateInner(getCachePerECFullUpdate(ecModel)); const axisStatAll: AxisStatAll = ecFullUpdateCache.all = createHashMap(); @@ -350,11 +352,10 @@ function performStatisticsForRecord( } if (!ecPrepareCacheMiss && ecPrepareLiPosMinGap != null) { // Consider the fact in practice: - // - Series data can only be changed in the "ec prepare" stage. - // - The relationship between series and axes can only be changed in "ec prepare" stage and - // `SERIES_FILTER`. - // (NOTE: "ec prepare" stage can be typically considered as `chart.setOption`, and "ec updated" - // stage can be typically considered as `dispatchAction`.) + // - Series data can only be changed in EC_PREPARE_UPDATE. + // - The relationship between series and axes can only be changed in EC_PREPARE_UPDATE and + // SERIES_FILTER. + // (See EC_CYCLE for more info) // Therefore, some statistics results can be cached in `GlobalModelCachePerECPrepare` to avoid // repeated time-consuming calculation for large data (e.g., over 1e5 data items). perKeyPerAxis.liPosMinGap = ecPrepareLiPosMinGap; @@ -452,7 +453,7 @@ export function requireAxisStatistics( callOnlyOnce(registers, function () { registers.registerProcessor(registers.PRIORITY.PROCESSOR.AXIS_STATISTICS, { - overallReset: performAxisStatistics + overallReset: performAxisStatisticsOnOverallReset }); }); } diff --git a/src/coord/scaleRawExtentInfo.ts b/src/coord/scaleRawExtentInfo.ts index d9d7ed6b4..f378b5da5 100644 --- a/src/coord/scaleRawExtentInfo.ts +++ b/src/coord/scaleRawExtentInfo.ts @@ -58,7 +58,7 @@ import { AxisStatKey, eachAxisStatKey } from './axisStatistics'; */ const scaleInner = makeInner<{ extent: number[]; - // series on this axis. + // series on this axis to union data extent. seriesList: SeriesModel[]; dimIdxInCoord: number; }, Scale>(); @@ -422,9 +422,6 @@ export class ScaleRawExtentInfo { * The outcome `_zoomMM` may have both `NullUndefined` and a finite value, like `[undefined, 123]`. */ setZoomMinMax(idxMinMax: 0 | 1, val: number | NullUndefined): void { - if (__DEV__) { - assert(this._i.zoomMM[idxMinMax] == null); - } this._i.zoomMM[idxMinMax] = val; } @@ -596,8 +593,11 @@ export function scaleRawExtentInfoReallyCreate( if (scale.rawExtentInfo) { if (__DEV__) { // Check for incorrect impl - the duplicated calling of this method is only allowed in - // one case: first dataZoom then coord sys update. - assert(scale.rawExtentInfo.from !== from); + // these cases: + // - First in `AxisProxy['reset']` (for dataZoom) + // - Then in `CoordinateSystem['update']`. + // - Then after `chart.appendData()` due to `dirtyOnOverallProgress: true` + assert(scale.rawExtentInfo.from !== from || from === AXIS_EXTENT_INFO_BUILD_FROM_DATA_ZOOM); } return; } diff --git a/src/core/ExtensionAPI.ts b/src/core/ExtensionAPI.ts index 047bb704c..cfba0a270 100644 --- a/src/core/ExtensionAPI.ts +++ b/src/core/ExtensionAPI.ts @@ -72,7 +72,7 @@ abstract class ExtensionAPI { abstract getViewOfComponentModel(componentModel: ComponentModel): ComponentView; abstract getViewOfSeriesModel(seriesModel: SeriesModel): ChartView; abstract getModel(): GlobalModel; - abstract getMainProcessVersion(): number; + abstract getECMainCycleVersion(): number; } export default ExtensionAPI; diff --git a/src/core/Scheduler.ts b/src/core/Scheduler.ts index 745631111..c87690e95 100644 --- a/src/core/Scheduler.ts +++ b/src/core/Scheduler.ts @@ -68,6 +68,8 @@ type TaskRecord = { overallTask?: OverallTask }; type PerformStageTaskOpt = { + // `block` means running from the beginning to the final end within + // an individual "progress". block?: boolean, setDirty?: boolean, visualType?: StageHandlerInternal['visualType'], @@ -96,7 +98,7 @@ interface OverallTaskContext extends TaskContext { } interface StubTaskContext extends TaskContext { model: SeriesModel; - overallProgress: boolean; + dirtyOnOverallProgress: boolean; }; class Scheduler { @@ -147,7 +149,7 @@ class Scheduler { // if a data processor depends on a component (e.g., dataZoomProcessor depends // on the settings of `dataZoom`), it should be re-performed if the component // is modified by `setOption`. - // (2) If a processor depends on sevral series, speicified by its `getTargetSeries`, + // (2) If a processor depends on several series, specified by its `getTargetSeries`, // it should be re-performed when the result array of `getTargetSeries` changed. // We use `dependencies` to cover these issues. // (3) How to update target series when coordinate system related components modified. @@ -488,15 +490,13 @@ class Scheduler { const seriesType = stageHandler.seriesType; const getTargetSeries = stageHandler.getTargetSeries; - let overallProgress = true; + const dirtyOnOverallProgress = stageHandler.dirtyOnOverallProgress; let shouldOverallTaskDirty = false; // FIXME:TS never used, so comment it // let modifyOutputEnd = stageHandler.modifyOutputEnd; // An overall task with seriesType detected or has `getTargetSeries`, we add - // stub in each pipelines, it will set the overall task dirty when the pipeline - // progress. Moreover, to avoid call the overall task each frame (too frequent), - // we set the pipeline block. + // stub in each pipelines to receive dirty info from upstream. let errMsg = ''; if (__DEV__) { errMsg = '"createOnAllSeries" is not supported for "overallReset", ' @@ -509,12 +509,7 @@ class Scheduler { else if (getTargetSeries) { getTargetSeries(ecModel, api).each(createStub); } - // Otherwise, (usually it is legacy case), the overall task will only be - // executed when upstream is dirty. Otherwise the progressive rendering of all - // pipelines will be disabled unexpectedly. But it still needs stubs to receive - // dirty info from upstream. else { - overallProgress = false; each(ecModel.getSeries(), createStub); } @@ -534,12 +529,12 @@ class Scheduler { ); stub.context = { model: seriesModel, - overallProgress: overallProgress + dirtyOnOverallProgress: dirtyOnOverallProgress // FIXME:TS never used, so comment it // modifyOutputEnd: modifyOutputEnd }; stub.agent = overallTask; - stub.__block = overallProgress; + stub.__block = dirtyOnOverallProgress; scheduler._pipe(seriesModel, stub); } @@ -586,7 +581,7 @@ function overallTaskReset(context: OverallTaskContext): void { } function stubReset(context: StubTaskContext): TaskProgressCallback<StubTaskContext> { - return context.overallProgress && stubProgress; + return context.dirtyOnOverallProgress && stubProgress; } function stubProgress(this: StubTask): void { diff --git a/src/core/echarts.ts b/src/core/echarts.ts index dc350f92c..ad5bb2bdc 100644 --- a/src/core/echarts.ts +++ b/src/core/echarts.ts @@ -139,6 +139,7 @@ import type geoSourceManager from '../coord/geo/geoSourceManager'; import { registerCustomSeries as registerCustom } from '../chart/custom/customSeriesRegister'; +import { resetCachePerECFullUpdate, resetCachePerECPrepare } from '../util/cycleCache'; declare let global: any; @@ -203,17 +204,68 @@ export const PRIORITY = { } }; -// Main process have three entries: `setOption`, `dispatchAction` and `resize`, -// where they must not be invoked nestedly, except the only case: invoke -// dispatchAction with updateMethod "none" in main process. -// This flag is used to carry out this rule. -// All events will be triggered out side main process (i.e. when !this[IN_MAIN_PROCESS]). -const IN_MAIN_PROCESS_KEY = '__flagInMainProcess' as const; +/** + * [ec updating/rendering cycles (EC_CYCLE)] + * + * - EC_MAIN_CYCLE: + * - It designates a run of a series of processing/updating/rendering. + * - It is triggered by: + * - `setOption` + * - `dispatchAction` + * (It is typically internally triggered by user inputs; but can also an explicit API call.) + * - `resize` + * - The next "animation frame" if in `lazyMode: true`. + * - Nested entry is not allowed. If triggering a new run of EC_MAIN_CYCLE during a + * unfinished run, the new run will be delayed until the current run finishes + * (if triggered by `dispatchAction`), or throw error (if triggered by other API calls). + * - All user-visible ec events are triggered outside EC_MAIN_CYCLE + * (i.e. be triggered after `this[IN_EC_MAIN_CYCLE_KEY]` becoming `false`). + * - A run of EC_MAIN_CYCLE comprises: + * - EC_PREPARE_UPDATE (may be absent) + * - EC_FULL_UPDATE or EC_PARTIAL_UPDATE + * - A run of EC_FULL_UPDATE comprises: + * - CoordinateSystem['create'] + * - Data processing (may be absent) (see `registerProcessor`) + * - CoordinateSystem['update'] (may be absent) + * - Visual encoding (may be absent) (see `registerVisual`) + * - Layout (may be absent) (see `registerLayout`) + * - Rendering (`ComponentView` or `SeriesView`) + * + * - EC_PROGRESSIVE_CYCLE: + * - It also carries out a series of processing/updating/rendering, but out of EC_MAIN_CYCLE. + * - It is performed in each "animation frame". + * - It can be triggered internally or `appendData` call. + * - A run of EC_PROGRESSIVE_CYCLE comprises: + * - Data processing (may be absent) (see `registerProcessor`) + * - Visual encoding (may be absent) (see `registerVisual`) + * - Layout (may be absent) (see `registerLayout`) + * - Rendering (`ComponentView` or `SeriesView`) + * - PENDING: currently all data processing tasks (via `registerProcessor`) run in "block" mode. + * (see `performDataProcessorTasks`) + * + * - Other updating/rendering cycles: + * - Some series have specific update/render cycles. For example, graph force layout performs + * layout and rendering in each "animation frame". + * + * - Model updating: + * - Model can only be modified at the beginning of ec cycles, including only: + * - EC_PREPARE_UPDATE (see method `prepare()`) in `setOption` call. + * - EC action handlers in `dispatchAction` call. + * - `appendData` (a special case, where only data is modified). + * + * - The lifetime of CoordinateSystem/Axis/Scale instances: + * - They are only re-created per run of EC_FULL_UPDATE. + * + * - Available caches: see `cycleCache.ts` + */ + +// See comments in EC_CYCLE. +const IN_EC_MAIN_CYCLE_KEY = '__flagInMainProcess' as const; // Useful for detecting outdated rendering results in scenarios that these issues are involved: -// - Use shortcut (such as, updateTransform, or no update) to start a main process. +// - Use EC_PARTIAL_UPDATE (such as, updateTransform, or no update) to start an EC_MAIN_CYCLE. // - Asynchronously update rendered view (e.g., graph force layout). // - Multiple ChartView/ComponentView render to one group cooperatively. -const MAIN_PROCESS_VERSION_KEY = '__mainProcessVersion' as const; +const EC_MAIN_CYCLE_VERSION_KEY = '__mainProcessVersion' as const; const PENDING_UPDATE = '__pendingUpdate' as const; const STATUS_NEEDS_UPDATE_KEY = '__needsUpdateStatus' as const; const ACTION_REG = /^[a-zA-Z0-9_]+$/; @@ -285,7 +337,7 @@ messageCenterProto.off = createRegisterEventWithLowercaseMessageCenter('off'); // --------------------------------------- // Internal method names for class ECharts // --------------------------------------- -let prepare: (ecIns: ECharts) => void; +let prepare: (ecIns: ECharts) => void; // This is `EC_PREPARE_UPDATE`. let prepareView: (ecIns: ECharts, isComponent: boolean) => void; let updateDirectly: ( ecIns: ECharts, method: string, payload: Payload, mainType: ComponentMainType, subType?: ComponentSubType @@ -293,11 +345,11 @@ let updateDirectly: ( type UpdateMethod = (this: ECharts, payload?: Payload, renderParams?: UpdateLifecycleParams) => void; let updateMethods: { prepareAndUpdate: UpdateMethod, - update: UpdateMethod, - updateTransform: UpdateMethod, - updateView: UpdateMethod, - updateVisual: UpdateMethod, - updateLayout: UpdateMethod + update: UpdateMethod, // This is `EC_FULL_UPDATE`. + updateTransform: UpdateMethod, // This is one of `EC_PARTIAL_UPDATE`. + updateView: UpdateMethod, // This is one of `EC_PARTIAL_UPDATE`. + updateVisual: UpdateMethod, // This is one of `EC_PARTIAL_UPDATE`. + updateLayout: UpdateMethod // This is one of `EC_PARTIAL_UPDATE`. }; let doConvertPixel: { ( @@ -345,7 +397,7 @@ let enableConnect: (ecIns: ECharts) => void; let markStatusToUpdate: (ecIns: ECharts) => void; let applyChangedStates: (ecIns: ECharts) => void; -let updateMainProcessVersion: (ecIns: ECharts) => void; +let updateECMainCycleVersion: (ecIns: ECharts) => void; type RenderedEventParam = { elapsedTime: number }; type ECEventDefinition = { @@ -427,8 +479,8 @@ class ECharts extends Eventful<ECEventDefinition> { silent: boolean updateParams: UpdateLifecycleParams }; - private [IN_MAIN_PROCESS_KEY]: boolean; - private [MAIN_PROCESS_VERSION_KEY]: number; + private [IN_EC_MAIN_CYCLE_KEY]: boolean; + private [EC_MAIN_CYCLE_VERSION_KEY]: number; private [CONNECT_STATUS_KEY]: ConnectStatus; private [STATUS_NEEDS_UPDATE_KEY]: boolean; @@ -451,7 +503,7 @@ class ECharts extends Eventful<ECEventDefinition> { let defaultCoarsePointer: 'auto' | boolean = 'auto'; let defaultUseDirtyRect = false; - this[MAIN_PROCESS_VERSION_KEY] = 1; + this[EC_MAIN_CYCLE_VERSION_KEY] = 1; if (__DEV__) { const root = ( @@ -545,15 +597,15 @@ class ECharts extends Eventful<ECEventDefinition> { if (this[PENDING_UPDATE]) { const silent = (this[PENDING_UPDATE] as any).silent; - this[IN_MAIN_PROCESS_KEY] = true; - updateMainProcessVersion(this); + this[IN_EC_MAIN_CYCLE_KEY] = true; + updateECMainCycleVersion(this); try { prepare(this); updateMethods.update.call(this, null, this[PENDING_UPDATE].updateParams); } catch (e) { - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; this[PENDING_UPDATE] = null; throw e; } @@ -566,7 +618,7 @@ class ECharts extends Eventful<ECEventDefinition> { // will render the final state of the elements before the real animation started. this._zr.flush(); - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; this[PENDING_UPDATE] = null; flushPendingActions.call(this, silent); @@ -649,7 +701,7 @@ class ECharts extends Eventful<ECEventDefinition> { setOption<Opt extends ECBasicOption>(option: Opt, opts?: SetOptionOpts): void; /* eslint-disable-next-line */ setOption<Opt extends ECBasicOption>(option: Opt, notMerge?: boolean | SetOptionOpts, lazyUpdate?: boolean): void { - if (this[IN_MAIN_PROCESS_KEY]) { + if (this[IN_EC_MAIN_CYCLE_KEY]) { if (__DEV__) { error('`setOption` should not be called during main process.'); } @@ -672,8 +724,8 @@ class ECharts extends Eventful<ECEventDefinition> { notMerge = notMerge.notMerge; } - this[IN_MAIN_PROCESS_KEY] = true; - updateMainProcessVersion(this); + this[IN_EC_MAIN_CYCLE_KEY] = true; + updateECMainCycleVersion(this); if (!this._model || notMerge) { const optionManager = new OptionManager(this._api); @@ -696,7 +748,7 @@ class ECharts extends Eventful<ECEventDefinition> { silent: silent, updateParams: updateParams }; - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; // `setOption(option, {lazyMode: true})` may be called when zrender has been slept. // It should wake it up to make sure zrender start to render at the next frame. @@ -709,7 +761,7 @@ class ECharts extends Eventful<ECEventDefinition> { } catch (e) { this[PENDING_UPDATE] = null; - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; throw e; } @@ -722,7 +774,7 @@ class ECharts extends Eventful<ECEventDefinition> { } this[PENDING_UPDATE] = null; - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; flushPendingActions.call(this, silent); triggerUpdatedEvent.call(this, silent); @@ -735,7 +787,7 @@ class ECharts extends Eventful<ECEventDefinition> { * @param opts Optional settings */ setTheme(theme: string | ThemeOption, opts?: SetThemeOpts): void { - if (this[IN_MAIN_PROCESS_KEY]) { + if (this[IN_EC_MAIN_CYCLE_KEY]) { if (__DEV__) { error('`setTheme` should not be called during main process.'); } @@ -763,8 +815,8 @@ class ECharts extends Eventful<ECEventDefinition> { this[PENDING_UPDATE] = null; } - this[IN_MAIN_PROCESS_KEY] = true; - updateMainProcessVersion(this); + this[IN_EC_MAIN_CYCLE_KEY] = true; + updateECMainCycleVersion(this); try { this._updateTheme(theme); @@ -774,11 +826,11 @@ class ECharts extends Eventful<ECEventDefinition> { updateMethods.update.call(this, {type: 'setTheme'}, updateParams); } catch (e) { - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; throw e; } - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; flushPendingActions.call(this, silent); triggerUpdatedEvent.call(this, silent); @@ -1357,7 +1409,7 @@ class ECharts extends Eventful<ECEventDefinition> { * Resize the chart */ resize(opts?: ResizeOpts): void { - if (this[IN_MAIN_PROCESS_KEY]) { + if (this[IN_EC_MAIN_CYCLE_KEY]) { if (__DEV__) { error('`resize` should not be called during main process.'); } @@ -1395,8 +1447,8 @@ class ECharts extends Eventful<ECEventDefinition> { this[PENDING_UPDATE] = null; } - this[IN_MAIN_PROCESS_KEY] = true; - updateMainProcessVersion(this); + this[IN_EC_MAIN_CYCLE_KEY] = true; + updateECMainCycleVersion(this); try { needPrepare && prepare(this); @@ -1409,11 +1461,11 @@ class ECharts extends Eventful<ECEventDefinition> { }); } catch (e) { - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; throw e; } - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; flushPendingActions.call(this, silent); @@ -1507,7 +1559,7 @@ class ECharts extends Eventful<ECEventDefinition> { } // May dispatchAction in rendering procedure - if (this[IN_MAIN_PROCESS_KEY]) { + if (this[IN_EC_MAIN_CYCLE_KEY]) { this._pendingActions.push(payload); return; } @@ -1579,7 +1631,7 @@ class ECharts extends Eventful<ECEventDefinition> { private static internalField = (function () { prepare = function (ecIns: ECharts): void { - modelUtil.resetCachePerECPrepare(ecIns._model); + resetCachePerECPrepare(ecIns._model); const scheduler = ecIns._scheduler; @@ -1806,7 +1858,7 @@ class ECharts extends Eventful<ECEventDefinition> { return; } - modelUtil.resetCachePerECFullUpdate(ecModel); + resetCachePerECFullUpdate(ecModel); ecModel.setUpdatePayload(payload); scheduler.restoreData(ecModel, payload); @@ -2050,8 +2102,8 @@ class ECharts extends Eventful<ECEventDefinition> { const updateMethod = cptTypeTmp.pop(); const cptType = cptTypeTmp[0] != null && parseClassType(cptTypeTmp[0]); - this[IN_MAIN_PROCESS_KEY] = true; - updateMainProcessVersion(this); + this[IN_EC_MAIN_CYCLE_KEY] = true; + updateECMainCycleVersion(this); let payloads: Payload[] = [payload]; let batched = false; @@ -2122,7 +2174,7 @@ class ECharts extends Eventful<ECEventDefinition> { } } catch (e) { - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; throw e; } } @@ -2139,7 +2191,7 @@ class ECharts extends Eventful<ECEventDefinition> { eventObj = eventObjBatch[0] as ECActionEvent; } - this[IN_MAIN_PROCESS_KEY] = false; + this[IN_EC_MAIN_CYCLE_KEY] = false; if (!silent) { let refinedEvent: ECActionEvent; @@ -2434,8 +2486,8 @@ class ECharts extends Eventful<ECEventDefinition> { ecIns.getZr().wakeUp(); }; - updateMainProcessVersion = function (ecIns: ECharts): void { - ecIns[MAIN_PROCESS_VERSION_KEY] = (ecIns[MAIN_PROCESS_VERSION_KEY] + 1) % 1000; + updateECMainCycleVersion = function (ecIns: ECharts): void { + ecIns[EC_MAIN_CYCLE_VERSION_KEY] = (ecIns[EC_MAIN_CYCLE_VERSION_KEY] + 1) % 1000; }; applyChangedStates = function (ecIns: ECharts): void { @@ -2666,8 +2718,8 @@ class ECharts extends Eventful<ECEventDefinition> { getViewOfSeriesModel(seriesModel: SeriesModel): ChartView { return ecIns.getViewOfSeriesModel(seriesModel); } - getMainProcessVersion(): number { - return ecIns[MAIN_PROCESS_VERSION_KEY]; + getECMainCycleVersion(): number { + return ecIns[EC_MAIN_CYCLE_VERSION_KEY]; } })(ecIns); }; diff --git a/src/core/task.ts b/src/core/task.ts index 6a25c1c12..20a59d3fb 100644 --- a/src/core/task.ts +++ b/src/core/task.ts @@ -112,7 +112,7 @@ export class Task<Ctx extends TaskContext> { // Injected in schedular __pipeline: Pipeline; __idxInPipeline: number; - __block: boolean; + __block: boolean; // FIXME: simplify it - merge with PerformStageTaskOpt['block']? // Context must be specified implicitly, to // avoid miss update context when model changed. @@ -242,6 +242,14 @@ export class Task<Ctx extends TaskContext> { return this.unfinished(); } + /** + * Generally, task dirty propagates to downstream tasks. + * Task dirty leads to the `reset` call, which discards the previous result and starts over + * the processing. + * + * See `StageHandler['reset']` and `StageHandler['overallReset']` for a summary of possible + * `dirty()` calls. + */ dirty(): void { this._dirty = true; this._onDirty && this._onDirty(this.context); diff --git a/src/label/LabelManager.ts b/src/label/LabelManager.ts index b60a9ac2b..411af3081 100644 --- a/src/label/LabelManager.ts +++ b/src/label/LabelManager.ts @@ -86,7 +86,7 @@ interface LabelDesc { * Save the original value determined in a pass of echarts main process. That refers to the values * rendered by `SeriesView`, before `series:layoutlabels` is triggered in `renderSeries`. * - * 'series:layoutlabels' may be triggered during some shortcut passes, such as zooming in series.graph/geo + * 'series:layoutlabels' may be triggered during some EC_PARTIAL_UPDATE passes, such as zooming in series.graph/geo * (`updateLabelLayout`), where the modified `Element` props should be restorable by the original value here. * * Regarding `Element` state, simply consider the values here as the normal state values. diff --git a/src/label/labelLayoutHelper.ts b/src/label/labelLayoutHelper.ts index 1136bb63d..856781118 100644 --- a/src/label/labelLayoutHelper.ts +++ b/src/label/labelLayoutHelper.ts @@ -509,7 +509,7 @@ export function restoreIgnore(labelList: LabelLayoutData[]): void { /** * [NOTICE - restore]: - * 'series:layoutlabels' may be triggered during some shortcut passes, such as zooming in series.graph/geo + * 'series:layoutlabels' may be triggered during some EC_PARTIAL_UPDATE passes, such as zooming in series.graph/geo * (`updateLabelLayout`), where the modified `Element` props should be restorable from `defaultAttr`. * @see `SavedLabelAttr` in `LabelManager.ts` * `restoreIgnore` can be called to perform the restore, if needed. diff --git a/src/model/OptionManager.ts b/src/model/OptionManager.ts index ef795ba58..846d3c215 100644 --- a/src/model/OptionManager.ts +++ b/src/model/OptionManager.ts @@ -17,12 +17,6 @@ * under the License. */ -/** - * ECharts option manager - */ - - -// import ComponentModel, { ComponentModelConstructor } from './Component'; import ExtensionAPI from '../core/ExtensionAPI'; import { OptionPreprocessor, MediaQuery, ECUnitOption, MediaUnit, ECBasicOption, SeriesOption diff --git a/src/scale/scaleMapper.ts b/src/scale/scaleMapper.ts index 76dd2e871..845594430 100644 --- a/src/scale/scaleMapper.ts +++ b/src/scale/scaleMapper.ts @@ -205,7 +205,7 @@ export interface ScaleMapperGeneric<This> { /** * [NOTICE]: - * In ec workflow, scale extent is finally determined at `coordSys#update` stage. + * In EC_MAIN_CYCLE, scale extent is finally determined at `coordSys#update` stage. * * Get a clone of the scale extent. * An extent is always in an increase order. @@ -230,7 +230,7 @@ export interface ScaleMapperGeneric<This> { * * `setExtent` is identical to `setExtent2(SCALE_EXTENT_KIND_EFFECTIVE)`. * - * [The steps of extent construction in ec workflow]: + * [The steps of extent construction in EC_MAIN_CYCLE]: * - step#1. At `CoordinateSystem#create` stage, requirements of collecting series data extents are * committed to `scaleRawExtentInfoRequireCreate`, and `Scale` instances are created. * - step#2. Call `scaleRawExtentInfoReallyCreate` to really collect series data extent and create diff --git a/src/util/cycleCache.ts b/src/util/cycleCache.ts new file mode 100644 index 000000000..7d653bb72 --- /dev/null +++ b/src/util/cycleCache.ts @@ -0,0 +1,73 @@ +/* +* 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 GlobalModel from '../model/Global'; +import { makeInner } from './model'; + + +const ecModelCacheInner = makeInner<{ + fullUpdate: GlobalModelCachePerECFullUpdate; + prepare: GlobalModelCachePerECPrepare; +}, GlobalModel>(); + +export type GlobalModelCachePerECPrepare = {__: 'prepare'}; // Nominal to distinguish. +export type GlobalModelCachePerECFullUpdate = {__: 'fullUpdate'}; // Nominal to distinguish. + +/** + * CAVEAT: Can only be called by `echarts.ts` + */ +export function resetCachePerECPrepare(ecModel: GlobalModel): void { + ecModelCacheInner(ecModel).prepare = {} as GlobalModelCachePerECPrepare; +} + +/** + * CAVEAT: Can only be called by `echarts.ts` + */ +export function resetCachePerECFullUpdate(ecModel: GlobalModel): void { + ecModelCacheInner(ecModel).fullUpdate = {} as GlobalModelCachePerECFullUpdate; +} + +/** + * The cache is auto cleared at the beginning of EC_PREPARE_UPDATE. + * See also comments in EC_CYCLE. + * + * NOTICE: + * - EC_PREPARE_UPDATE is not necessarily executed before each EC_FULL_UPDATE performing. + * Typically, `setOption` trigger EC_PREPARE_UPDATE, but `dispatchAction` does not. + * - It is not cleared in EC_PARTIAL_UPDATE and EC_PROGRESSIVE_CYCLE. + * + */ +export function getCachePerECPrepare(ecModel: GlobalModel): GlobalModelCachePerECPrepare { + return ecModelCacheInner(ecModel).prepare; +} + +/** + * The cache is auto cleared at the beginning of EC_FULL_UPDATE. + * See also comments in EC_CYCLE. + * + * NOTICE: + * - It is not cleared in EC_PARTIAL_UPDATE and EC_PROGRESSIVE_CYCLE. + * - The cache should NOT be written before EC_FULL_UPDATE started, such as: + * - should NOT in `getTargetSeries` methods of data processors. + * - should NOT in `init`/`mergeOption`/`optionUpdated`/`getData` methods of component/series models. + * - See `getCachePerECPrepare` for details. + */ +export function getCachePerECFullUpdate(ecModel: GlobalModel): GlobalModelCachePerECFullUpdate { + return ecModelCacheInner(ecModel).fullUpdate; +} diff --git a/src/util/model.ts b/src/util/model.ts index 5412ee058..95d7df3d6 100644 --- a/src/util/model.ts +++ b/src/util/model.ts @@ -30,7 +30,6 @@ import { isStringSafe, isNumber, hasOwn, - isTypedArray, } from 'zrender/src/core/util'; import env from 'zrender/src/core/env'; import GlobalModel from '../model/Global'; @@ -1274,55 +1273,6 @@ export function makeCallOnlyOnce<Host extends object>() { let onceUniqueIndex = getRandomIdBase(); -const ecModelCacheInner = makeInner<{ - fullUpdate: GlobalModelCachePerECFullUpdate; - prepare: GlobalModelCachePerECPrepare; -}, GlobalModel>(); - -export type GlobalModelCachePerECPrepare = {__: 'prepare'}; // Nominal to distinguish. -export type GlobalModelCachePerECFullUpdate = {__: 'fullUpdate'}; // Nominal to distinguish. - -/** - * CAVEAT: Can only be called by `echarts.ts` - */ -export function resetCachePerECPrepare(ecModel: GlobalModel): void { - ecModelCacheInner(ecModel).prepare = {} as GlobalModelCachePerECPrepare; -} - -/** - * CAVEAT: Can only be called by `echarts.ts` - */ -export function resetCachePerECFullUpdate(ecModel: GlobalModel): void { - ecModelCacheInner(ecModel).fullUpdate = {} as GlobalModelCachePerECFullUpdate; -} - -/** - * The cache is auto cleared at the begining of a run of "ec prepare". - * Typically, `setOption` trigger "ec prepare", but `dispatchAction` does not. - * - * NOTICE: - * - "ec prepare" is not necessarily performed before each "ec full update" performing. - */ -export function getCachePerECPrepare(ecModel: GlobalModel): GlobalModelCachePerECPrepare { - return ecModelCacheInner(ecModel).prepare; -} - -/** - * The cache is auto cleared at the begining of a run of "ec full update". - * However, all shortcuts (such as `updateView`/`updateLayout`/etc.) do not clear it. - * Typically, all `setOption` and some `dispatchAction` trigger "ec full update". - * This is the same as the lifecycle of coordinate systems instances and axes instances. - * - * NOTICE: - * - The cache should NOT be written in: - * - `getTargetSeries` methods of data processors. - * - `init`/`mergeOption`/`optionUpdated`/`getData` methods of component/series models. - * See `getCachePerECPrepare` for details. - */ -export function getCachePerECFullUpdate(ecModel: GlobalModel): GlobalModelCachePerECFullUpdate { - return ecModelCacheInner(ecModel).fullUpdate; -} - /** * @usage * - The earlier item takes precedence for duplicate items. diff --git a/src/util/types.ts b/src/util/types.ts index ad6e962ad..aebc93ce6 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -336,19 +336,25 @@ export interface StageHandlerOverallReset { (ecModel: GlobalModel, api: ExtensionAPI, payload?: Payload): void } export interface StageHandler { + /** - * Indicate that the task will be piped all series - * (`performRawSeries` indicate whether includes filtered series). + * Indicate that the "series stage task" will be piped for all series + * (filtered series is included iff `performRawSeries: true`). + * + * OVERALL_STAGE_TASK (See `overallReset`) can not set `createOnAllSeries: true`. */ createOnAllSeries?: boolean; + /** - * Indicate that the task will be only piped in the pipeline of this type of series. - * (`performRawSeries` indicate whether includes filtered series). + * Indicate that the task will only be piped in the pipeline of this type of series. + * (filtered series is included iff `performRawSeries: true`). + * It is available for both `reset` and `overallReset`. */ seriesType?: string; + /** - * Indicate that the task will be only piped in the pipeline of the returned series. - * Called in "prepare" stage, before coord sys creation. + * Indicate that the task will only be piped in the pipeline of the returned series. + * It is called in EC_PREPARE_UPDATE, before `CoordinateSystem['create']`. * It is available for both `reset` and `overallReset`. */ getTargetSeries?: (ecModel: GlobalModel, api: ExtensionAPI) => HashMap<SeriesModel>; @@ -362,18 +368,45 @@ export interface StageHandler { * Called only when this task in a pipeline. */ plan?: StageHandlerPlan; + /** - * If `overallReset` specified, an "overall task" will be created. - * "overall task" does not belong to a certain pipeline. - * They always be "performed" in certain phase (depends on when they declared). - * They has "stub"s to connect with pipelines (one stub for one pipeline), - * delivering info like "dirty" and "output end". + * If `overallReset` is specified, an OVERALL_STAGE_TASK will be created. + * An OVERALL_STAGE_TASK resides across multiple pipelines, and is associated with + * pipelines by "stub"s, which deliver messages like "dirty" and "output end". + * OVERALL_STAGE_TASK does not support `progess` method. + * + * The `overallReset` method is called iff this task is "dirty" (See `Task['dirty']`). + * See `StageHandler['reset']` for a summary of possible `dirty()` calls. */ overallReset?: StageHandlerOverallReset; + /** - * Called only when this task in a single pipeline, and "dirty". + * If `reset` is specified, a SERIES_STAGE_TASK will be created. + * A SERIES_STAGE_TASK is owned by a pipeline and is specific to a single series. + * + * The `reset` method is called iff this task is "dirty" (See `Task['dirty']`). + * Task `dirty()` call typically originates from: + * - A trigger of EC_MAIN_CYCLE (including EC_FULL_UPDATE and EC_PARTIAL_UPDATE) + * (See comments in EC_CYCLE) + * - NOTICE: `dirtyOnOverallProgress: true` cause that the corresponding `overallReset` + * and `reset` of downsteams tasks may also be called in EC_PROGRESSIVE_CYCLE. + * But in this case, `CoordinateSystem#create` and `CoordinateSystem#update` are + * not called. */ reset?: StageHandlerReset; + + /** + * This is a temporary mechanism for dataZoom case in `appendData`. + * + * It will set the OVERALL_STAGE_TASK dirty when the pipeline progress. + * Moreover, to avoid call the OVERALL_STAGE_TASK each frame (too frequent), + * it set the pipeline block (via `task.__block`) in this stage. + * + * Otherwise, (usually it is legacy case), the OVERALL_STAGE_TASK will only be + * executed when upstream is dirty. Otherwise the progressive rendering of all + * pipelines will be disabled unexpectedly. + */ + dirtyOnOverallProgress?: boolean; } export interface StageHandlerInternal extends StageHandler { diff --git a/test/bar-polar-multi-series-radial.html b/test/bar-polar-multi-series-radial.html index 5185b2551..73374c2a5 100644 --- a/test/bar-polar-multi-series-radial.html +++ b/test/bar-polar-multi-series-radial.html @@ -34,7 +34,7 @@ under the License. </style> - <!-- <div id="main_multiple_bar_category_angle_axis"></div> --> + <div id="main_multiple_bar_category_angle_axis"></div> <div id="main_multiple_bar_value_angle_axis"></div> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
