| /* | |
| * 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 env from 'zrender/src/core/env'; | |
| import * as modelUtil from '../util/model'; | |
| import { | |
| DataHost, DimensionName, StageHandlerProgressParams, | |
| SeriesOption, ZRColor, BoxLayoutOptionMixin, | |
| ScaleDataValue, | |
| Dictionary, | |
| OptionDataItemObject, | |
| SeriesDataType, | |
| SeriesEncodeOptionMixin, | |
| OptionEncodeValue, | |
| ColorBy, | |
| StatesOptionMixin | |
| } from '../util/types'; | |
| import ComponentModel, { ComponentModelConstructor } from './Component'; | |
| import {PaletteMixin} from './mixin/palette'; | |
| import { DataFormatMixin } from '../model/mixin/dataFormat'; | |
| import Model from '../model/Model'; | |
| import { | |
| getLayoutParams, | |
| mergeLayoutParam, | |
| fetchLayoutMode | |
| } from '../util/layout'; | |
| import {createTask} from '../core/task'; | |
| import GlobalModel from './Global'; | |
| import { CoordinateSystem } from '../coord/CoordinateSystem'; | |
| import { ExtendableConstructor, mountExtend, Constructor } from '../util/clazz'; | |
| import { PipelineContext, SeriesTaskContext, GeneralTask, OverallTask, SeriesTask } from '../core/Scheduler'; | |
| import LegendVisualProvider from '../visual/LegendVisualProvider'; | |
| import SeriesData from '../data/SeriesData'; | |
| import Axis from '../coord/Axis'; | |
| import type { BrushCommonSelectorsForSeries, BrushSelectableArea } from '../component/brush/selector'; | |
| import makeStyleMapper from './mixin/makeStyleMapper'; | |
| import { SourceManager } from '../data/helper/sourceManager'; | |
| import { Source } from '../data/Source'; | |
| import { defaultSeriesFormatTooltip } from '../component/tooltip/seriesFormatTooltip'; | |
| import {ECSymbol} from '../util/symbol'; | |
| import {Group} from '../util/graphic'; | |
| import {LegendIconParams} from '../component/legend/LegendModel'; | |
| import {dimPermutations} from '../component/marker/MarkAreaView'; | |
| const inner = modelUtil.makeInner<{ | |
| data: SeriesData | |
| dataBeforeProcessed: SeriesData | |
| sourceManager: SourceManager | |
| }, SeriesModel>(); | |
| function getSelectionKey(data: SeriesData, dataIndex: number): string { | |
| return data.getName(dataIndex) || data.getId(dataIndex); | |
| } | |
| export const SERIES_UNIVERSAL_TRANSITION_PROP = '__universalTransitionEnabled'; | |
| interface SeriesModel { | |
| /** | |
| * Convenient for override in extended class. | |
| * Implement it if needed. | |
| */ | |
| preventIncremental(): boolean; | |
| /** | |
| * See tooltip. | |
| * Implement it if needed. | |
| * @return Point of tooltip. null/undefined can be returned. | |
| */ | |
| getTooltipPosition(dataIndex: number): number[]; | |
| /** | |
| * Get data indices for show tooltip content. See tooltip. | |
| * Implement it if needed. | |
| */ | |
| getAxisTooltipData( | |
| dim: DimensionName[], | |
| value: ScaleDataValue, | |
| baseAxis: Axis | |
| ): { | |
| dataIndices: number[], | |
| nestestValue: any | |
| }; | |
| /** | |
| * Get position for marker | |
| */ | |
| getMarkerPosition( | |
| value: ScaleDataValue[], | |
| dims?: typeof dimPermutations[number], | |
| startingAtTick?: boolean | |
| ): number[]; | |
| /** | |
| * Get legend icon symbol according to each series type | |
| */ | |
| getLegendIcon(opt: LegendIconParams): ECSymbol | Group; | |
| /** | |
| * See `component/brush/selector.js` | |
| * Defined the brush selector for this series. | |
| */ | |
| brushSelector( | |
| dataIndex: number, | |
| data: SeriesData, | |
| selectors: BrushCommonSelectorsForSeries, | |
| area: BrushSelectableArea | |
| ): boolean; | |
| enableAriaDecal(): void; | |
| } | |
| class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentModel<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: string; | |
| // Should be implenented in subclass. | |
| defaultOption: SeriesOption; | |
| // @readonly | |
| seriesIndex: number; | |
| // coodinateSystem will be injected in the echarts/CoordinateSystem | |
| coordinateSystem: CoordinateSystem; | |
| // Injected outside | |
| dataTask: SeriesTask; | |
| // Injected outside | |
| pipelineContext: PipelineContext; | |
| // --------------------------------------- | |
| // Props to tell visual/style.ts about how to do visual encoding. | |
| // --------------------------------------- | |
| // legend visual provider to the legend component | |
| legendVisualProvider: LegendVisualProvider; | |
| // Access path of style for visual | |
| visualStyleAccessPath: string; | |
| // Which property is treated as main color. Which can get from the palette. | |
| visualDrawType: 'fill' | 'stroke'; | |
| // Style mapping rules. | |
| visualStyleMapper: ReturnType<typeof makeStyleMapper>; | |
| // If ignore style on data. It's only for global visual/style.ts | |
| // Enabled when series it self will handle it. | |
| ignoreStyleOnData: boolean; | |
| // If do symbol visual encoding | |
| hasSymbolVisual: boolean; | |
| // Default symbol type. | |
| defaultSymbol: string; | |
| // Symbol provide to legend. | |
| legendIcon: string; | |
| // It will be set temporary when cross series transition setting is from setOption. | |
| // TODO if deprecate further? | |
| [SERIES_UNIVERSAL_TRANSITION_PROP]: boolean; | |
| // --------------------------------------- | |
| // Props about data selection | |
| // --------------------------------------- | |
| private _selectedDataIndicesMap: Dictionary<number> = {}; | |
| readonly preventUsingHoverLayer: boolean; | |
| static protoInitialize = (function () { | |
| const proto = SeriesModel.prototype; | |
| proto.type = 'series.__base__'; | |
| proto.seriesIndex = 0; | |
| proto.ignoreStyleOnData = false; | |
| proto.hasSymbolVisual = false; | |
| proto.defaultSymbol = 'circle'; | |
| // Make sure the values can be accessed! | |
| proto.visualStyleAccessPath = 'itemStyle'; | |
| proto.visualDrawType = 'fill'; | |
| })(); | |
| init(option: Opt, parentModel: Model, ecModel: GlobalModel) { | |
| this.seriesIndex = this.componentIndex; | |
| this.dataTask = createTask<SeriesTaskContext>({ | |
| count: dataTaskCount, | |
| reset: dataTaskReset | |
| }); | |
| this.dataTask.context = {model: this}; | |
| this.mergeDefaultAndTheme(option, ecModel); | |
| const sourceManager = inner(this).sourceManager = new SourceManager(this); | |
| sourceManager.prepareSource(); | |
| const data = this.getInitialData(option, ecModel); | |
| wrapData(data, this); | |
| this.dataTask.context.data = data; | |
| if (__DEV__) { | |
| zrUtil.assert(data, 'getInitialData returned invalid data.'); | |
| } | |
| inner(this).dataBeforeProcessed = data; | |
| // If we reverse the order (make data firstly, and then make | |
| // dataBeforeProcessed by cloneShallow), cloneShallow will | |
| // cause data.graph.data !== data when using | |
| // module:echarts/data/Graph or module:echarts/data/Tree. | |
| // See module:echarts/data/helper/linkSeriesData | |
| // Theoretically, it is unreasonable to call `seriesModel.getData()` in the model | |
| // init or merge stage, because the data can be restored. So we do not `restoreData` | |
| // and `setData` here, which forbids calling `seriesModel.getData()` in this stage. | |
| // Call `seriesModel.getRawData()` instead. | |
| // this.restoreData(); | |
| autoSeriesName(this); | |
| this._initSelectedMapFromData(data); | |
| } | |
| /** | |
| * Util for merge default and theme to option | |
| */ | |
| mergeDefaultAndTheme(option: Opt, ecModel: GlobalModel): void { | |
| const layoutMode = fetchLayoutMode(this); | |
| const inputPositionParams = layoutMode | |
| ? getLayoutParams(option as BoxLayoutOptionMixin) : {}; | |
| // Backward compat: using subType on theme. | |
| // But if name duplicate between series subType | |
| // (for example: parallel) add component mainType, | |
| // add suffix 'Series'. | |
| let themeSubType = this.subType; | |
| if ((ComponentModel as ComponentModelConstructor).hasClass(themeSubType)) { | |
| themeSubType += 'Series'; | |
| } | |
| zrUtil.merge( | |
| option, | |
| ecModel.getTheme().get(this.subType) | |
| ); | |
| zrUtil.merge(option, this.getDefaultOption()); | |
| // Default label emphasis `show` | |
| modelUtil.defaultEmphasis(option, 'label', ['show']); | |
| this.fillDataTextStyle(option.data as ArrayLike<any>); | |
| if (layoutMode) { | |
| mergeLayoutParam(option as BoxLayoutOptionMixin, inputPositionParams, layoutMode); | |
| } | |
| } | |
| mergeOption(newSeriesOption: Opt, ecModel: GlobalModel) { | |
| // this.settingTask.dirty(); | |
| newSeriesOption = zrUtil.merge(this.option, newSeriesOption, true); | |
| this.fillDataTextStyle(newSeriesOption.data as ArrayLike<any>); | |
| const layoutMode = fetchLayoutMode(this); | |
| if (layoutMode) { | |
| mergeLayoutParam( | |
| this.option as BoxLayoutOptionMixin, | |
| newSeriesOption as BoxLayoutOptionMixin, | |
| layoutMode | |
| ); | |
| } | |
| const sourceManager = inner(this).sourceManager; | |
| sourceManager.dirty(); | |
| sourceManager.prepareSource(); | |
| const data = this.getInitialData(newSeriesOption, ecModel); | |
| wrapData(data, this); | |
| this.dataTask.dirty(); | |
| this.dataTask.context.data = data; | |
| inner(this).dataBeforeProcessed = data; | |
| autoSeriesName(this); | |
| this._initSelectedMapFromData(data); | |
| } | |
| fillDataTextStyle(data: ArrayLike<any>): void { | |
| // Default data label emphasis `show` | |
| // FIXME Tree structure data ? | |
| // FIXME Performance ? | |
| if (data && !zrUtil.isTypedArray(data)) { | |
| const props = ['show']; | |
| for (let i = 0; i < data.length; i++) { | |
| if (data[i] && data[i].label) { | |
| modelUtil.defaultEmphasis(data[i], 'label', props); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Init a data structure from data related option in series | |
| * Must be overridden. | |
| */ | |
| getInitialData(option: Opt, ecModel: GlobalModel): SeriesData { | |
| return; | |
| } | |
| /** | |
| * Append data to list | |
| */ | |
| appendData(params: {data: ArrayLike<any>}): void { | |
| // FIXME ??? | |
| // (1) If data from dataset, forbidden append. | |
| // (2) support append data of dataset. | |
| const data = this.getRawData(); | |
| data.appendData(params.data); | |
| } | |
| /** | |
| * Consider some method like `filter`, `map` need make new data, | |
| * We should make sure that `seriesModel.getData()` get correct | |
| * data in the stream procedure. So we fetch data from upstream | |
| * each time `task.perform` called. | |
| */ | |
| getData(dataType?: SeriesDataType): SeriesData<this> { | |
| const task = getCurrentTask(this); | |
| if (task) { | |
| const data = task.context.data; | |
| return (dataType == null ? data : data.getLinkedData(dataType)) as SeriesData<this>; | |
| } | |
| else { | |
| // When series is not alive (that may happen when click toolbox | |
| // restore or setOption with not merge mode), series data may | |
| // be still need to judge animation or something when graphic | |
| // elements want to know whether fade out. | |
| return inner(this).data as SeriesData<this>; | |
| } | |
| } | |
| getAllData(): ({ | |
| data: SeriesData, | |
| type?: SeriesDataType | |
| })[] { | |
| const mainData = this.getData(); | |
| return (mainData && mainData.getLinkedDataAll) | |
| ? mainData.getLinkedDataAll() | |
| : [{ data: mainData }]; | |
| } | |
| setData(data: SeriesData): void { | |
| const task = getCurrentTask(this); | |
| if (task) { | |
| const context = task.context; | |
| // Consider case: filter, data sample. | |
| // FIXME:TS never used, so comment it | |
| // if (context.data !== data && task.modifyOutputEnd) { | |
| // task.setOutputEnd(data.count()); | |
| // } | |
| context.outputData = data; | |
| // Caution: setData should update context.data, | |
| // Because getData may be called multiply in a | |
| // single stage and expect to get the data just | |
| // set. (For example, AxisProxy, x y both call | |
| // getData and setDate sequentially). | |
| // So the context.data should be fetched from | |
| // upstream each time when a stage starts to be | |
| // performed. | |
| if (task !== this.dataTask) { | |
| context.data = data; | |
| } | |
| } | |
| inner(this).data = data; | |
| } | |
| getEncode() { | |
| const encode = (this as Model<SeriesEncodeOptionMixin>).get('encode', true); | |
| if (encode) { | |
| return zrUtil.createHashMap<OptionEncodeValue, DimensionName>(encode); | |
| } | |
| } | |
| getSourceManager(): SourceManager { | |
| return inner(this).sourceManager; | |
| } | |
| getSource(): Source { | |
| return this.getSourceManager().getSource(); | |
| } | |
| /** | |
| * Get data before processed | |
| */ | |
| getRawData(): SeriesData { | |
| return inner(this).dataBeforeProcessed; | |
| } | |
| getColorBy(): ColorBy { | |
| const colorBy = this.get('colorBy'); | |
| return colorBy || 'series'; | |
| } | |
| isColorBySeries(): boolean { | |
| return this.getColorBy() === 'series'; | |
| } | |
| /** | |
| * Get base axis if has coordinate system and has axis. | |
| * By default use coordSys.getBaseAxis(); | |
| * Can be overridden for some chart. | |
| * @return {type} description | |
| */ | |
| getBaseAxis(): Axis { | |
| const coordSys = this.coordinateSystem; | |
| // @ts-ignore | |
| return coordSys && coordSys.getBaseAxis && coordSys.getBaseAxis(); | |
| } | |
| /** | |
| * Default tooltip formatter | |
| * | |
| * @param dataIndex | |
| * @param multipleSeries | |
| * @param dataType | |
| * @param renderMode valid values: 'html'(by default) and 'richText'. | |
| * 'html' is used for rendering tooltip in extra DOM form, and the result | |
| * string is used as DOM HTML content. | |
| * 'richText' is used for rendering tooltip in rich text form, for those where | |
| * DOM operation is not supported. | |
| * @return formatted tooltip with `html` and `markers` | |
| * Notice: The override method can also return string | |
| */ | |
| formatTooltip( | |
| dataIndex: number, | |
| multipleSeries?: boolean, | |
| dataType?: SeriesDataType | |
| ): ReturnType<DataFormatMixin['formatTooltip']> { | |
| return defaultSeriesFormatTooltip({ | |
| series: this, | |
| dataIndex: dataIndex, | |
| multipleSeries: multipleSeries | |
| }); | |
| } | |
| isAnimationEnabled(): boolean { | |
| const ecModel = this.ecModel; | |
| // Disable animation if using echarts in node but not give ssr flag. | |
| // In ssr mode, renderToString will generate svg with css animation. | |
| if (env.node && !(ecModel && ecModel.ssr)) { | |
| return false; | |
| } | |
| let animationEnabled = this.getShallow('animation'); | |
| if (animationEnabled) { | |
| if (this.getData().count() > this.getShallow('animationThreshold')) { | |
| animationEnabled = false; | |
| } | |
| } | |
| return !!animationEnabled; | |
| } | |
| restoreData() { | |
| this.dataTask.dirty(); | |
| } | |
| getColorFromPalette(name: string, scope: any, requestColorNum?: number): ZRColor { | |
| const ecModel = this.ecModel; | |
| // PENDING | |
| let color = PaletteMixin.prototype.getColorFromPalette.call(this, name, scope, requestColorNum); | |
| if (!color) { | |
| color = ecModel.getColorFromPalette(name, scope, requestColorNum); | |
| } | |
| return color; | |
| } | |
| /** | |
| * Use `data.mapDimensionsAll(coordDim)` instead. | |
| * @deprecated | |
| */ | |
| coordDimToDataDim(coordDim: DimensionName): DimensionName[] { | |
| return this.getRawData().mapDimensionsAll(coordDim); | |
| } | |
| /** | |
| * Get progressive rendering count each step | |
| */ | |
| getProgressive(): number | false { | |
| return this.get('progressive'); | |
| } | |
| /** | |
| * Get progressive rendering count each step | |
| */ | |
| getProgressiveThreshold(): number { | |
| return this.get('progressiveThreshold'); | |
| } | |
| // PENGING If selectedMode is null ? | |
| select(innerDataIndices: number[], dataType?: SeriesDataType): void { | |
| this._innerSelect(this.getData(dataType), innerDataIndices); | |
| } | |
| unselect(innerDataIndices: number[], dataType?: SeriesDataType): void { | |
| const selectedMap = this.option.selectedMap; | |
| if (!selectedMap) { | |
| return; | |
| } | |
| const selectedMode = this.option.selectedMode; | |
| const data = this.getData(dataType); | |
| if (selectedMode === 'series' || selectedMap === 'all') { | |
| this.option.selectedMap = {}; | |
| this._selectedDataIndicesMap = {}; | |
| return; | |
| } | |
| for (let i = 0; i < innerDataIndices.length; i++) { | |
| const dataIndex = innerDataIndices[i]; | |
| const nameOrId = getSelectionKey(data, dataIndex); | |
| selectedMap[nameOrId] = false; | |
| this._selectedDataIndicesMap[nameOrId] = -1; | |
| } | |
| } | |
| toggleSelect(innerDataIndices: number[], dataType?: SeriesDataType): void { | |
| const tmpArr: number[] = []; | |
| for (let i = 0; i < innerDataIndices.length; i++) { | |
| tmpArr[0] = innerDataIndices[i]; | |
| this.isSelected(innerDataIndices[i], dataType) | |
| ? this.unselect(tmpArr, dataType) | |
| : this.select(tmpArr, dataType); | |
| } | |
| } | |
| getSelectedDataIndices(): number[] { | |
| if (this.option.selectedMap === 'all') { | |
| return [].slice.call(this.getData().getIndices()); | |
| } | |
| const selectedDataIndicesMap = this._selectedDataIndicesMap; | |
| const nameOrIds = zrUtil.keys(selectedDataIndicesMap); | |
| const dataIndices = []; | |
| for (let i = 0; i < nameOrIds.length; i++) { | |
| const dataIndex = selectedDataIndicesMap[nameOrIds[i]]; | |
| if (dataIndex >= 0) { | |
| dataIndices.push(dataIndex); | |
| } | |
| } | |
| return dataIndices; | |
| } | |
| isSelected(dataIndex: number, dataType?: SeriesDataType): boolean { | |
| const selectedMap = this.option.selectedMap; | |
| if (!selectedMap) { | |
| return false; | |
| } | |
| const data = this.getData(dataType); | |
| return (selectedMap === 'all' || selectedMap[getSelectionKey(data, dataIndex)]) | |
| && !data.getItemModel<StatesOptionMixin<unknown, unknown>>(dataIndex).get(['select', 'disabled']); | |
| } | |
| isUniversalTransitionEnabled(): boolean { | |
| if (this[SERIES_UNIVERSAL_TRANSITION_PROP]) { | |
| return true; | |
| } | |
| const universalTransitionOpt = this.option.universalTransition; | |
| // Quick reject | |
| if (!universalTransitionOpt) { | |
| return false; | |
| } | |
| if (universalTransitionOpt === true) { | |
| return true; | |
| } | |
| // Can be simply 'universalTransition: true' | |
| return universalTransitionOpt && universalTransitionOpt.enabled; | |
| } | |
| private _innerSelect(data: SeriesData, innerDataIndices: number[]) { | |
| const option = this.option; | |
| const selectedMode = option.selectedMode; | |
| const len = innerDataIndices.length; | |
| if (!selectedMode || !len) { | |
| return; | |
| } | |
| if (selectedMode === 'series') { | |
| option.selectedMap = 'all'; | |
| } | |
| else if (selectedMode === 'multiple') { | |
| if (!zrUtil.isObject(option.selectedMap)) { | |
| option.selectedMap = {}; | |
| } | |
| const selectedMap = option.selectedMap; | |
| for (let i = 0; i < len; i++) { | |
| const dataIndex = innerDataIndices[i]; | |
| // TODO different types of data share same object. | |
| const nameOrId = getSelectionKey(data, dataIndex); | |
| selectedMap[nameOrId] = true; | |
| this._selectedDataIndicesMap[nameOrId] = data.getRawIndex(dataIndex); | |
| } | |
| } | |
| else if (selectedMode === 'single' || selectedMode === true) { | |
| const lastDataIndex = innerDataIndices[len - 1]; | |
| const nameOrId = getSelectionKey(data, lastDataIndex); | |
| option.selectedMap = { | |
| [nameOrId]: true | |
| }; | |
| this._selectedDataIndicesMap = { | |
| [nameOrId]: data.getRawIndex(lastDataIndex) | |
| }; | |
| } | |
| } | |
| private _initSelectedMapFromData(data: SeriesData) { | |
| // Ignore select info in data if selectedMap exists. | |
| // NOTE It's only for legacy usage. edge data is not supported. | |
| if (this.option.selectedMap) { | |
| return; | |
| } | |
| const dataIndices: number[] = []; | |
| if (data.hasItemOption) { | |
| data.each(function (idx) { | |
| const rawItem = data.getRawDataItem(idx); | |
| if (rawItem && (rawItem as OptionDataItemObject<unknown>).selected) { | |
| dataIndices.push(idx); | |
| } | |
| }); | |
| } | |
| if (dataIndices.length > 0) { | |
| this._innerSelect(data, dataIndices); | |
| } | |
| } | |
| // /** | |
| // * @see {module:echarts/stream/Scheduler} | |
| // */ | |
| // abstract pipeTask: null | |
| static registerClass(clz: Constructor): Constructor { | |
| return ComponentModel.registerClass(clz); | |
| } | |
| } | |
| interface SeriesModel<Opt extends SeriesOption = SeriesOption> | |
| extends DataFormatMixin, PaletteMixin<Opt>, DataHost { | |
| // methods that can be implemented optionally to provide to components | |
| /** | |
| * Get dimension to render shadow in dataZoom component | |
| */ | |
| getShadowDim?(): string | |
| } | |
| zrUtil.mixin(SeriesModel, DataFormatMixin); | |
| zrUtil.mixin(SeriesModel, PaletteMixin); | |
| export type SeriesModelConstructor = typeof SeriesModel & ExtendableConstructor; | |
| mountExtend(SeriesModel, ComponentModel as SeriesModelConstructor); | |
| /** | |
| * MUST be called after `prepareSource` called | |
| * Here we need to make auto series, especially for auto legend. But we | |
| * do not modify series.name in option to avoid side effects. | |
| */ | |
| function autoSeriesName(seriesModel: SeriesModel): void { | |
| // User specified name has higher priority, otherwise it may cause | |
| // series can not be queried unexpectedly. | |
| const name = seriesModel.name; | |
| if (!modelUtil.isNameSpecified(seriesModel)) { | |
| seriesModel.name = getSeriesAutoName(seriesModel) || name; | |
| } | |
| } | |
| function getSeriesAutoName(seriesModel: SeriesModel): string { | |
| const data = seriesModel.getRawData(); | |
| const dataDims = data.mapDimensionsAll('seriesName'); | |
| const nameArr: string[] = []; | |
| zrUtil.each(dataDims, function (dataDim) { | |
| const dimInfo = data.getDimensionInfo(dataDim); | |
| dimInfo.displayName && nameArr.push(dimInfo.displayName); | |
| }); | |
| return nameArr.join(' '); | |
| } | |
| function dataTaskCount(context: SeriesTaskContext): number { | |
| return context.model.getRawData().count(); | |
| } | |
| function dataTaskReset(context: SeriesTaskContext) { | |
| const seriesModel = context.model; | |
| seriesModel.setData(seriesModel.getRawData().cloneShallow()); | |
| return dataTaskProgress; | |
| } | |
| function dataTaskProgress(param: StageHandlerProgressParams, context: SeriesTaskContext): void { | |
| // Avoid repead cloneShallow when data just created in reset. | |
| if (context.outputData && param.end > context.outputData.count()) { | |
| context.model.getRawData().cloneShallow(context.outputData); | |
| } | |
| } | |
| // TODO refactor | |
| function wrapData(data: SeriesData, seriesModel: SeriesModel): void { | |
| zrUtil.each(zrUtil.concatArray(data.CHANGABLE_METHODS, data.DOWNSAMPLE_METHODS), function (methodName) { | |
| data.wrapMethod(methodName as any, zrUtil.curry(onDataChange, seriesModel)); | |
| }); | |
| } | |
| function onDataChange(this: SeriesData, seriesModel: SeriesModel, newList: SeriesData): SeriesData { | |
| const task = getCurrentTask(seriesModel); | |
| if (task) { | |
| // Consider case: filter, selectRange | |
| task.setOutputEnd((newList || this).count()); | |
| } | |
| return newList; | |
| } | |
| function getCurrentTask(seriesModel: SeriesModel): GeneralTask { | |
| const scheduler = (seriesModel.ecModel || {}).scheduler; | |
| const pipeline = scheduler && scheduler.getPipeline(seriesModel.uid); | |
| if (pipeline) { | |
| // When pipline finished, the currrentTask keep the last | |
| // task (renderTask). | |
| let task = pipeline.currentTask; | |
| if (task) { | |
| const agentStubMap = (task as OverallTask).agentStubMap; | |
| if (agentStubMap) { | |
| task = agentStubMap.get(seriesModel.uid); | |
| } | |
| } | |
| return task; | |
| } | |
| } | |
| export default SeriesModel; |