List.ts 77.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
* 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.
*/

S
sushuang 已提交
20 21
/* global Float64Array, Int32Array, Uint32Array, Uint16Array */

L
lang 已提交
22 23 24
/**
 * List for data storage
 */
L
lang 已提交
25

S
sushuang 已提交
26
import * as zrUtil from 'zrender/src/core/util';
S
sushuang 已提交
27 28
import Model from '../model/Model';
import DataDiffer from './DataDiffer';
29
import {DefaultDataProvider, DataProvider} from './helper/dataProvider';
30
import {summarizeDimensions, DimensionSummary} from './helper/dimensionHelper';
S
fix:  
SHUANG SU 已提交
31
import DataDimensionInfo from './DataDimensionInfo';
32 33 34
import {ArrayLike, Dictionary, FunctionPropertyNames} from 'zrender/src/core/types';
import Element from 'zrender/src/Element';
import {
35
    DimensionIndex, DimensionName, DimensionLoose, OptionDataItem,
36 37
    ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput,
    ModelOption, SeriesDataType, OrdinalRawValue
38
} from '../util/types';
39
import {isDataItemOption, convertOptionIdName} from '../util/model';
40
import { getECData } from '../util/innerStore';
41
import { PathStyleProps } from 'zrender/src/graphic/Path';
P
pissang 已提交
42 43
import type Graph from './Graph';
import type Tree from './Tree';
44
import type { VisualMeta } from '../component/visualMap/VisualMapModel';
45
import { parseDataValue } from './helper/dataValueHelper';
1
fix:  
100pah 已提交
46
import { isSourceInstance } from './Source';
47

48
const mathFloor = Math.floor;
49
const isObject = zrUtil.isObject;
P
pissang 已提交
50
const map = zrUtil.map;
S
sushuang 已提交
51

52 53
const UNDEFINED = 'undefined';
const INDEX_NOT_FOUND = -1;
S
sushuang 已提交
54

55 56
// Use prefix to avoid index to be the same as otherIdList[idx],
// which will cause weird udpate animation.
57
const ID_PREFIX = 'e\0\0';
58

59
const dataCtors = {
S
sushuang 已提交
60 61 62 63
    'float': typeof Float64Array === UNDEFINED
        ? Array : Float64Array,
    'int': typeof Int32Array === UNDEFINED
        ? Array : Int32Array,
S
sushuang 已提交
64 65 66 67 68 69
    // Ordinal data type can be string or int
    'ordinal': Array,
    'number': Array,
    'time': Array
};

70 71
export type ListDimensionType = keyof typeof dataCtors;

S
sushuang 已提交
72 73
// Caution: MUST not use `new CtorUint32Array(arr, 0, len)`, because the Ctor of array is
// different from the Ctor of typed array.
74 75 76
const CtorUint32Array = typeof Uint32Array === UNDEFINED ? Array : Uint32Array;
const CtorInt32Array = typeof Int32Array === UNDEFINED ? Array : Int32Array;
const CtorUint16Array = typeof Uint16Array === UNDEFINED ? Array : Uint16Array;
P
pissang 已提交
77

78 79 80 81 82 83 84 85 86 87 88
type DataTypedArray = Uint32Array | Int32Array | Uint16Array | Float64Array;
type DataTypedArrayConstructor = typeof Uint32Array | typeof Int32Array | typeof Uint16Array | typeof Float64Array;
type DataArrayLikeConstructor = typeof Array | DataTypedArrayConstructor;


type DimValueGetter = (
    this: List,
    dataItem: any,
    dimName: DimensionName,
    dataIndex: number,
    dimIndex: DimensionIndex
89
) => ParsedValue;
90

91
type DataValueChunk = ArrayLike<ParsedValue>;
P
pissang 已提交
92
type DataStorage = {[dimName: string]: DataValueChunk};
93 94 95 96 97 98 99
type NameRepeatCount = {[name: string]: number};


type ItrParamDims = DimensionLoose | Array<DimensionLoose>;
// If Ctx not specified, use List as Ctx
type CtxOrList<Ctx> = unknown extends Ctx ? List : Ctx;
type EachCb0<Ctx> = (this: CtxOrList<Ctx>, idx: number) => void;
100 101
type EachCb1<Ctx> = (this: CtxOrList<Ctx>, x: ParsedValue, idx: number) => void;
type EachCb2<Ctx> = (this: CtxOrList<Ctx>, x: ParsedValue, y: ParsedValue, idx: number) => void;
102 103
type EachCb<Ctx> = (this: CtxOrList<Ctx>, ...args: any) => void;
type FilterCb0<Ctx> = (this: CtxOrList<Ctx>, idx: number) => boolean;
104 105
type FilterCb1<Ctx> = (this: CtxOrList<Ctx>, x: ParsedValue, idx: number) => boolean;
type FilterCb2<Ctx> = (this: CtxOrList<Ctx>, x: ParsedValue, y: ParsedValue, idx: number) => boolean;
106 107
type FilterCb<Ctx> = (this: CtxOrList<Ctx>, ...args: any) => boolean;
type MapArrayCb0<Ctx> = (this: CtxOrList<Ctx>, idx: number) => any;
108 109
type MapArrayCb1<Ctx> = (this: CtxOrList<Ctx>, x: ParsedValue, idx: number) => any;
type MapArrayCb2<Ctx> = (this: CtxOrList<Ctx>, x: ParsedValue, y: ParsedValue, idx: number) => any;
110
type MapArrayCb<Ctx> = (this: CtxOrList<Ctx>, ...args: any) => any;
111 112 113 114
type MapCb1<Ctx> = (this: CtxOrList<Ctx>, x: ParsedValue, idx: number) => ParsedValue | ParsedValue[];
type MapCb2<Ctx> = (this: CtxOrList<Ctx>, x: ParsedValue, y: ParsedValue, idx: number) =>
    ParsedValue | ParsedValue[];
type MapCb<Ctx> = (this: CtxOrList<Ctx>, ...args: any) => ParsedValue | ParsedValue[];
115

S
sushuang 已提交
116

117
const TRANSFERABLE_PROPERTIES = [
118
    'hasItemOption', '_nameList', '_idList', '_invertedIndicesMap',
119
    '_rawData', '_dimValueGetter',
120 121
    '_count', '_rawCount', '_nameDimIdx', '_idDimIdx'
];
122
const CLONE_PROPERTIES = [
123
    '_extent', '_approximateExtent', '_rawExtent'
S
sushuang 已提交
124 125
];

126 127
export interface DefaultDataVisual {
    style: PathStyleProps
128 129
    // Draw type determined which prop should be set with encoded color.
    // It's only available on the global visual. Use getVisual('drawType') to access it.
130
    // It will be set in visual/style.ts module in the first priority.
131
    drawType: 'fill' | 'stroke'
132 133 134

    symbol?: string
    symbolSize?: number | number[]
135
    symbolRotate?: number
136 137 138 139 140 141 142 143
    symbolKeepAspect?: boolean

    liftZ?: number
    // For legend.
    legendSymbol?: string

    // visualMap will inject visualMeta data
    visualMeta?: VisualMeta[]
144 145 146

    // If color is encoded from palette
    colorFromPalette?: boolean
147
}
L
lang 已提交
148

149 150 151 152 153 154 155 156 157
export interface DataCalculationInfo<SERIES_MODEL> {
    stackedDimension: string;
    stackedByDimension: string;
    isStackedByIndex: boolean;
    stackedOverDimension: string;
    stackResultDimension: string;
    stackedOnSeries?: SERIES_MODEL;
}

158 159 160 161 162
// -----------------------------
// Internal method declarations:
// -----------------------------
let defaultDimValueGetters: {[sourceFormat: string]: DimValueGetter};
let prepareInvertedIndex: (list: List) => void;
163
let getRawValueFromStore: (list: List, dimIndex: number, rawIndex: number) => ParsedValue | OrdinalRawValue;
164
let getIndicesCtor: (list: List) => DataArrayLikeConstructor;
P
pissang 已提交
165 166
let prepareStorage: (
    storage: DataStorage, dimInfo: DataDimensionInfo, end: number, append?: boolean
167 168 169 170 171 172 173 174 175 176 177
) => void;
let getRawIndexWithoutIndices: (this: List, idx: number) => number;
let getRawIndexWithIndices: (this: List, idx: number) => number;
let getId: (list: List, rawIndex: number) => string;
let normalizeDimensions: (dimensions: ItrParamDims) => Array<DimensionLoose>;
let validateDimensions: (list: List, dims: DimensionName[]) => void;
let cloneListForMapAndSample: (original: List, excludeDimensions: DimensionName[]) => List;
let getInitialExtent: () => [number, number];
let setItemDataAndSeriesIndex: (this: Element, child: Element) => void;
let transferProperties: (target: List, source: List) => void;

178 179 180 181
class List<
    HostModel extends Model = Model,
    Visual extends DefaultDataVisual = DefaultDataVisual
> {
L
lang 已提交
182

183
    readonly type = 'list';
L
lang 已提交
184

185
    readonly dimensions: string[];
186

187 188
    // Infomation of each data dimension, like data type.
    private _dimensionInfos: {[dimName: string]: DataDimensionInfo};
189

P
pissang 已提交
190
    readonly hostModel: HostModel;
191

192 193 194 195
    /**
     * @readonly
     */
    dataType: SeriesDataType;
196

P
pissang 已提交
197
    /**
198
     * @readonly
P
pissang 已提交
199 200
     * Host graph if List is used to store graph nodes / edges.
     */
201 202
    graph?: Graph;

P
pissang 已提交
203
    /**
204
     * @readonly
P
pissang 已提交
205 206
     * Host tree if List is used to store tree ndoes.
     */
207
    tree?: Tree;
P
pissang 已提交
208

209 210 211
    // Indices stores the indices of data subset after filtered.
    // This data subset will be used in chart.
    private _indices: ArrayLike<any>;
S
sushuang 已提交
212

213 214 215
    private _count: number = 0;
    private _rawCount: number = 0;
    private _storage: DataStorage = {};
P
pissang 已提交
216 217 218 219
    // We have an extra array store here. It's faster to be acessed than KV structured `_storage`.
    // We profile the code `storage[dim]` and it seems to be KeyedLoadIC_Megamorphic instead of fast property access.
    // Not sure why this happens. But using an extra array seems leads to faster `initData`
    // See https://github.com/apache/incubator-echarts/pull/13314 for more explaination.
P
pissang 已提交
220
    private _storageArr: DataValueChunk[] = [];
221 222
    private _nameList: string[] = [];
    private _idList: string[] = [];
S
sushuang 已提交
223

224 225 226
    // Models of data option is stored sparse for optimizing memory cost
    // Never used yet (not used yet).
    // private _optionModels: Model[] = [];
S
sushuang 已提交
227

228 229
    // Global visual properties after visual coding
    private _visual: Dictionary<any> = {};
S
sushuang 已提交
230

231 232
    // Globel layout properties.
    private _layout: Dictionary<any> = {};
S
sushuang 已提交
233

234 235
    // Item visual properties after visual coding
    private _itemVisuals: Dictionary<any>[] = [];
S
sushuang 已提交
236

237 238
    // Item layout properties after layout
    private _itemLayouts: any[] = [];
S
sushuang 已提交
239

240 241
    // Graphic elemnents
    private _graphicEls: Element[] = [];
O
Ovilia 已提交
242

243
    private _rawData: DataProvider;
P
pah100 已提交
244

245 246 247
    // Raw extent will not be cloned, but only transfered.
    // It will not be calculated util needed.
    private _rawExtent: {[dimName: string]: [number, number]} = {};
L
lang 已提交
248

249
    private _extent: {[dimName: string]: [number, number]} = {};
L
lang 已提交
250

251 252
    // key: dim, value: extent
    private _approximateExtent: {[dimName: string]: [number, number]} = {};
S
sushuang 已提交
253

254
    private _dimensionsSummary: DimensionSummary;
P
pah100 已提交
255

256
    private _invertedIndicesMap: {[dimName: string]: ArrayLike<number>};
L
lang 已提交
257

258
    private _calculationInfo: DataCalculationInfo<HostModel> = {} as DataCalculationInfo<HostModel>;
P
pah100 已提交
259

260 261 262 263 264 265 266 267
    // User output info of this data.
    // DO NOT use it in other places!
    // When preparing user params for user callbacks, we have
    // to clone these inner data structures to prevent users
    // from modifying them to effect built-in logic. And for
    // performance consideration we make this `userOutput` to
    // avoid clone them too many times.
    readonly userOutput: DimensionUserOuput;
O
Ovilia 已提交
268

269 270
    // If each data item has it's own option
    hasItemOption: boolean = true;
S
sushuang 已提交
271

272 273 274 275
    // @readonly
    defaultDimValueGetter: DimValueGetter;
    private _dimValueGetter: DimValueGetter;
    private _dimValueGetterArrayRows: DimValueGetter;
L
lang 已提交
276

277 278 279
    private _nameRepeatCount: NameRepeatCount;
    private _nameDimIdx: number;
    private _idDimIdx: number;
S
sushuang 已提交
280

281
    private __wrappedMethods: string[];
S
sushuang 已提交
282

283 284
    // Methods that create a new list based on this list should be listed here.
    // Notice that those method should `RETURN` the new list.
285
    TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'lttbDownSample', 'map'] as const;
286
    // Methods that change indices of this list should be listed here.
287
    CHANGABLE_METHODS = ['filterSelf', 'selectRange'] as const;
288
    DOWNSAMPLE_METHODS = ['downSample', 'lttbDownSample'] as const;
289 290

    /**
291 292 293
     * @param dimensions
     *        For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...].
     *        Dimensions should be concrete names like x, y, z, lng, lat, angle, radius
294
     */
P
pissang 已提交
295
    constructor(dimensions: Array<string | object | DataDimensionInfo>, hostModel: HostModel) {
296 297
        dimensions = dimensions || ['x', 'y'];

298 299 300
        const dimensionInfos: Dictionary<DataDimensionInfo> = {};
        const dimensionNames = [];
        const invertedIndicesMap: Dictionary<number[]> = {};
301

302
        for (let i = 0; i < dimensions.length; i++) {
303
            // Use the original dimensions[i], where other flag props may exists.
304
            const dimInfoInput = dimensions[i];
305

306
            const dimensionInfo: DataDimensionInfo =
307 308 309 310 311 312
                zrUtil.isString(dimInfoInput)
                ? new DataDimensionInfo({name: dimInfoInput})
                : !(dimInfoInput instanceof DataDimensionInfo)
                ? new DataDimensionInfo(dimInfoInput)
                : dimInfoInput;

313
            const dimensionName = dimensionInfo.name;
314 315 316 317 318
            dimensionInfo.type = dimensionInfo.type || 'float';
            if (!dimensionInfo.coordDim) {
                dimensionInfo.coordDim = dimensionName;
                dimensionInfo.coordDimIndex = 0;
            }
319

320 321 322
            dimensionInfo.otherDims = dimensionInfo.otherDims || {};
            dimensionNames.push(dimensionName);
            dimensionInfos[dimensionName] = dimensionInfo;
L
lang 已提交
323

324
            dimensionInfo.index = i;
S
sushuang 已提交
325

326 327 328 329
            if (dimensionInfo.createInvertedIndices) {
                invertedIndicesMap[dimensionName] = [];
            }
        }
S
sushuang 已提交
330

331 332 333 334 335 336 337 338 339 340 341
        this.dimensions = dimensionNames;
        this._dimensionInfos = dimensionInfos;
        this.hostModel = hostModel;

        // Cache summary info for fast visit. See "dimensionHelper".
        this._dimensionsSummary = summarizeDimensions(this);

        this._invertedIndicesMap = invertedIndicesMap;

        this.userOutput = this._dimensionsSummary.userOutput;
    }
342 343

    /**
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
     * The meanings of the input parameter `dim`:
     *
     * + If dim is a number (e.g., `1`), it means the index of the dimension.
     *   For example, `getDimension(0)` will return 'x' or 'lng' or 'radius'.
     * + If dim is a number-like string (e.g., `"1"`):
     *     + If there is the same concrete dim name defined in `this.dimensions`, it means that concrete name.
     *     + If not, it will be converted to a number, which means the index of the dimension.
     *        (why? because of the backward compatbility. We have been tolerating number-like string in
     *        dimension setting, although now it seems that it is not a good idea.)
     *     For example, `visualMap[i].dimension: "1"` is the same meaning as `visualMap[i].dimension: 1`,
     *     if no dimension name is defined as `"1"`.
     * + If dim is a not-number-like string, it means the concrete dim name.
     *   For example, it can be be default name `"x"`, `"y"`, `"z"`, `"lng"`, `"lat"`, `"angle"`, `"radius"`,
     *   or customized in `dimensions` property of option like `"age"`.
     *
     * Get dimension name
     * @param dim See above.
     * @return Concrete dim name.
362
     */
363 364 365 366 367 368 369 370 371
    getDimension(dim: DimensionLoose): DimensionName {
        if (typeof dim === 'number'
            // If being a number-like string but not being defined a dimension name.
            || (!isNaN(dim as any) && !this._dimensionInfos.hasOwnProperty(dim))
        ) {
            dim = this.dimensions[dim as DimensionIndex];
        }
        return dim as DimensionName;
    }
S
sushuang 已提交
372 373

    /**
374 375 376 377
     * Get type and calculation info of particular dimension
     * @param dim
     *        Dimension can be concrete names like x, y, z, lng, lat, angle, radius
     *        Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius'
S
sushuang 已提交
378
     */
379 380 381 382
    getDimensionInfo(dim: DimensionLoose): DataDimensionInfo {
        // Do not clone, because there may be categories in dimInfo.
        return this._dimensionInfos[this.getDimension(dim)];
    }
S
sushuang 已提交
383 384

    /**
385
     * concrete dimension name list on coord.
S
sushuang 已提交
386
     */
387 388 389
    getDimensionsOnCoord(): DimensionName[] {
        return this._dimensionsSummary.dataDimsOnCoord.slice();
    }
390 391

    /**
392 393
     * @param coordDim
     * @param idx A coordDim may map to more than one data dim.
394 395
     *        If not specified, return the first dim not extra.
     * @return concrete data dim. If not found, return null/undefined
396
     */
397 398
    mapDimension(coordDim: DimensionName): DimensionName;
    mapDimension(coordDim: DimensionName, idx: number): DimensionName;
399
    mapDimension(coordDim: DimensionName, idx?: number): DimensionName {
400
        const dimensionsSummary = this._dimensionsSummary;
401 402 403 404

        if (idx == null) {
            return dimensionsSummary.encodeFirstDimNotExtra[coordDim] as any;
        }
L
lang 已提交
405

406
        const dims = dimensionsSummary.encode[coordDim];
407 408 409 410 411 412 413
        return dims ? dims[idx as number] as any : null;
    }

    mapDimensionsAll(coordDim: DimensionName): DimensionName[] {
        const dimensionsSummary = this._dimensionsSummary;
        const dims = dimensionsSummary.encode[coordDim];
        return (dims || []).slice();
S
sushuang 已提交
414
    }
L
lang 已提交
415

416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
    /**
     * Initialize from data
     * @param data source or data or data provider.
     * @param nameLIst The name of a datum is used on data diff and
     *        defualt label/tooltip.
     *        A name can be specified in encode.itemName,
     *        or dataItem.name (only for series option data),
     *        or provided in nameList from outside.
     */
    initData(
        data: any,
        nameList?: string[],
        dimValueGetter?: DimValueGetter
    ): void {

1
fix:  
100pah 已提交
431
        const notProvider = isSourceInstance(data) || zrUtil.isArrayLike(data);
432 433 434
        if (notProvider) {
            data = new DefaultDataProvider(data, this.dimensions.length);
        }
L
lang 已提交
435

436 437 438 439 440 441 442
        if (__DEV__) {
            if (!notProvider
                && (typeof data.getItem !== 'function' || typeof data.count !== 'function')
            ) {
                throw new Error('Inavlid data provider.');
            }
        }
S
sushuang 已提交
443

444
        this._rawData = data;
S
sushuang 已提交
445

446 447 448
        // Clear
        this._storage = {};
        this._indices = null;
S
sushuang 已提交
449

450
        this._nameList = nameList || [];
451

452
        this._idList = [];
453

454
        this._nameRepeatCount = {};
455

456 457
        if (!dimValueGetter) {
            this.hasItemOption = false;
L
lang 已提交
458
        }
459

460 461 462 463 464 465 466
        this.defaultDimValueGetter = defaultDimValueGetters[
            this._rawData.getSource().sourceFormat
        ];
        // Default dim value getter
        this._dimValueGetter = dimValueGetter = dimValueGetter
            || this.defaultDimValueGetter;
        this._dimValueGetterArrayRows = defaultDimValueGetters.arrayRows;
L
lang 已提交
467

468 469
        // Reset raw extent.
        this._rawExtent = {};
L
lang 已提交
470

471
        this._initDataFromProvider(0, data.count());
L
lang 已提交
472

473 474 475 476 477
        // If data has no item option.
        if (data.pure) {
            this.hasItemOption = false;
        }
    }
L
lang 已提交
478

479 480
    getProvider(): DataProvider {
        return this._rawData;
S
sushuang 已提交
481
    }
482 483

    /**
484
     * Caution: Can be only called on raw data (before `this._indices` created).
485
     */
486 487 488 489
    appendData(data: ArrayLike<any>): void {
        if (__DEV__) {
            zrUtil.assert(!this._indices, 'appendData can only be called on raw data.');
        }
S
sushuang 已提交
490

491 492
        const rawData = this._rawData;
        const start = this.count();
493
        rawData.appendData(data);
494
        let end = rawData.count();
495 496 497
        if (!rawData.persistent) {
            end += start;
        }
P
pissang 已提交
498
        this._initDataFromProvider(start, end, true);
499
    }
500

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
    /**
     * Caution: Can be only called on raw data (before `this._indices` created).
     * This method does not modify `rawData` (`dataProvider`), but only
     * add values to storage.
     *
     * The final count will be increased by `Math.max(values.length, names.length)`.
     *
     * @param values That is the SourceType: 'arrayRows', like
     *        [
     *            [12, 33, 44],
     *            [NaN, 43, 1],
     *            ['-', 'asdf', 0]
     *        ]
     *        Each item is exaclty cooresponding to a dimension.
     */
    appendValues(values: any[][], names?: string[]): void {
517 518 519 520
        const storage = this._storage;
        const dimensions = this.dimensions;
        const dimLen = dimensions.length;
        const rawExtent = this._rawExtent;
521

522 523
        const start = this.count();
        const end = start + Math.max(values.length, names ? names.length : 0);
524

525
        for (let i = 0; i < dimLen; i++) {
526
            const dim = dimensions[i];
527 528 529
            if (!rawExtent[dim]) {
                rawExtent[dim] = getInitialExtent();
            }
P
pissang 已提交
530
            prepareStorage(storage, this._dimensionInfos[dim], end, true);
S
sushuang 已提交
531
        }
532

P
pissang 已提交
533
        const rawExtentArr = map(dimensions, (dim) => {
534 535 536
            return rawExtent[dim];
        });

P
pissang 已提交
537 538 539 540 541
        const storageArr = this._storageArr = map(dimensions, (dim) => {
            return storage[dim];
        });

        const emptyDataItem: number[] = [];
542
        for (let idx = start; idx < end; idx++) {
543
            const sourceIdx = idx - start;
544
            // Store the data by dimensions
545 546
            for (let dimIdx = 0; dimIdx < dimLen; dimIdx++) {
                const dim = dimensions[dimIdx];
547
                const val = this._dimValueGetterArrayRows(
548
                    values[sourceIdx] || emptyDataItem, dim, sourceIdx, dimIdx
549
                ) as ParsedValueNumeric;
P
pissang 已提交
550
                storageArr[dimIdx][idx] = val;
551

552
                const dimRawExtent = rawExtentArr[dimIdx];
553 554 555
                val < dimRawExtent[0] && (dimRawExtent[0] = val);
                val > dimRawExtent[1] && (dimRawExtent[1] = val);
            }
556

557 558 559
            if (names) {
                this._nameList[idx] = names[sourceIdx];
            }
560 561
        }

562
        this._rawCount = this._count = end;
563

564 565
        // Reset data extent
        this._extent = {};
566

567 568
        prepareInvertedIndex(this);
    }
569

P
pissang 已提交
570
    private _initDataFromProvider(start: number, end: number, append?: boolean): void {
571 572 573
        if (start >= end) {
            return;
        }
574

575 576 577 578 579 580 581 582 583
        const rawData = this._rawData;
        const storage = this._storage;
        const dimensions = this.dimensions;
        const dimLen = dimensions.length;
        const dimensionInfoMap = this._dimensionInfos;
        const nameList = this._nameList;
        const idList = this._idList;
        const rawExtent = this._rawExtent;
        const nameRepeatCount: NameRepeatCount = this._nameRepeatCount = {};
584 585 586
        let nameDimIdx;

        for (let i = 0; i < dimLen; i++) {
587
            const dim = dimensions[i];
588 589 590
            if (!rawExtent[dim]) {
                rawExtent[dim] = getInitialExtent();
            }
S
sushuang 已提交
591

592
            const dimInfo = dimensionInfoMap[dim];
593 594 595 596 597 598
            if (dimInfo.otherDims.itemName === 0) {
                nameDimIdx = this._nameDimIdx = i;
            }
            if (dimInfo.otherDims.itemId === 0) {
                this._idDimIdx = i;
            }
S
sushuang 已提交
599

P
pissang 已提交
600
            prepareStorage(storage, dimInfo, end, append);
S
sushuang 已提交
601
        }
602

P
pissang 已提交
603 604
        const storageArr = this._storageArr = map(dimensions, (dim) => {
            return storage[dim];
605
        });
606

P
pissang 已提交
607 608 609
        const rawExtentArr = map(dimensions, (dim) => {
            return rawExtent[dim];
        });
610

P
pissang 已提交
611 612
        if (rawData.fillStorage) {
            rawData.fillStorage(start, end, storageArr, rawExtentArr);
P
pissang 已提交
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
        }
        else {
            let dataItem = [] as OptionDataItem;
            for (let idx = start; idx < end; idx++) {
                // NOTICE: Try not to write things into dataItem
                dataItem = rawData.getItem(idx, dataItem);
                // Each data item is value
                // [1, 2]
                // 2
                // Bar chart, line chart which uses category axis
                // only gives the 'y' value. 'x' value is the indices of category
                // Use a tempValue to normalize the value to be a (x, y) value

                // Store the data by dimensions
                for (let dimIdx = 0; dimIdx < dimLen; dimIdx++) {
                    const dim = dimensions[dimIdx];
                    const dimStorage = storageArr[dimIdx];
                    // PENDING NULL is empty or zero
                    const val = this._dimValueGetter(dataItem, dim, idx, dimIdx) as ParsedValueNumeric;
                    dimStorage[idx] = val;

                    const dimRawExtent = rawExtentArr[dimIdx];
                    val < dimRawExtent[0] && (dimRawExtent[0] = val);
                    val > dimRawExtent[1] && (dimRawExtent[1] = val);
                }
S
sushuang 已提交
638

P
pissang 已提交
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
                // ??? FIXME not check by pure but sourceFormat?
                // TODO refactor these logic.
                if (!rawData.pure) {
                    let name: string = nameList[idx];

                    if (dataItem && name == null) {
                        // If dataItem is {name: ...}, it has highest priority.
                        // That is appropriate for many common cases.
                        if ((dataItem as any).name != null) {
                            // There is no other place to persistent dataItem.name,
                            // so save it to nameList.
                            nameList[idx] = name = convertOptionIdName((dataItem as any).name, null);
                        }
                        else if (nameDimIdx != null) {
                            const nameDim = dimensions[nameDimIdx];
                            const nameDimChunk = storage[nameDim];
                            if (nameDimChunk) {
                                const ordinalMeta = dimensionInfoMap[nameDim].ordinalMeta;
                                name = convertOptionIdName(
                                    (ordinalMeta && ordinalMeta.categories.length)
                                        ? ordinalMeta.categories[nameDimChunk[idx] as number]
                                        : nameDimChunk[idx],
                                    null
                                );
                            }
664 665
                        }
                    }
S
sushuang 已提交
666

P
pissang 已提交
667 668 669
                    // Try using the id in option
                    // id or name is used on dynamical data, mapping old and new items.
                    let id: string = dataItem == null ? null : convertOptionIdName((dataItem as any).id, null);
S
sushuang 已提交
670

P
pissang 已提交
671 672 673 674 675 676 677 678
                    if (id == null && name != null) {
                        // Use name as id and add counter to avoid same name
                        nameRepeatCount[name] = nameRepeatCount[name] || 0;
                        id = name;
                        if (nameRepeatCount[name] > 0) {
                            id += '__ec__' + nameRepeatCount[name];
                        }
                        nameRepeatCount[name]++;
679
                    }
P
pissang 已提交
680
                    id != null && (idList[idx] = id);
S
sushuang 已提交
681 682 683
                }
            }
        }
684 685 686 687
        if (!rawData.persistent && rawData.clean) {
            // Clean unused data if data source is typed array.
            rawData.clean();
        }
P
pissang 已提交
688

689
        this._rawCount = this._count = end;
S
sushuang 已提交
690

691 692
        // Reset data extent
        this._extent = {};
S
tweak  
sushuang 已提交
693

694
        prepareInvertedIndex(this);
695 696
    }

697 698
    count(): number {
        return this._count;
699 700
    }

701
    getIndices(): ArrayLike<number> {
702
        let newIndices;
S
sushuang 已提交
703

704
        const indices = this._indices;
705
        if (indices) {
706 707
            const Ctor = indices.constructor as DataArrayLikeConstructor;
            const thisCount = this._count;
708 709 710
            // `new Array(a, b, c)` is different from `new Uint32Array(a, b, c)`.
            if (Ctor === Array) {
                newIndices = new Ctor(thisCount);
1
100pah 已提交
711
                for (let i = 0; i < thisCount; i++) {
712 713
                    newIndices[i] = indices[i];
                }
S
sushuang 已提交
714
            }
715 716 717 718
            else {
                newIndices = new (Ctor as DataTypedArrayConstructor)(
                    (indices as DataTypedArray).buffer, 0, thisCount
                );
S
sushuang 已提交
719 720
            }
        }
721
        else {
722
            const Ctor = getIndicesCtor(this);
723
            newIndices = new Ctor(this.count());
1
100pah 已提交
724
            for (let i = 0; i < newIndices.length; i++) {
725
                newIndices[i] = i;
726
            }
727 728
        }

729 730
        return newIndices;
    }
731

732 733 734 735
    // Get data by index of dimension.
    // Because in v8 access array by number variable is faster than access object by string variable
    // Not sure why but the optimization just works.
    getByDimIdx(dimIdx: number, idx: number): ParsedValue {
736 737 738
        if (!(idx >= 0 && idx < this._count)) {
            return NaN;
        }
739
        const dimStore = this._storageArr[dimIdx];
P
pissang 已提交
740
        return dimStore ? dimStore[this.getRawIndex(idx)] : NaN;
741 742
    }

743 744 745 746
    /**
     * Get value. Return NaN if idx is out of range.
     * @param dim Dim must be concrete name.
     */
747
    get(dim: DimensionName, idx: number): ParsedValue {
748 749
        if (!(idx >= 0 && idx < this._count)) {
            return NaN;
S
sushuang 已提交
750
        }
751
        const dimStore = this._storage[dim];
P
pissang 已提交
752
        return dimStore ? dimStore[this.getRawIndex(idx)] : NaN;
753
    }
S
sushuang 已提交
754

755 756 757
    /**
     * @param dim concrete dim
     */
758
    getByRawIndex(dim: DimensionName, rawIdx: number): ParsedValue {
759 760 761
        if (!(rawIdx >= 0 && rawIdx < this._rawCount)) {
            return NaN;
        }
762
        const dimStore = this._storage[dim];
P
pissang 已提交
763
        return dimStore ? dimStore[rawIdx] : NaN;
764
    }
P
pissang 已提交
765

766 767 768 769
    /**
     * Get value for multi dimensions.
     * @param dimensions If ignored, using all dimensions.
     */
770
    getValues(idx: number): ParsedValue[];
771 772
    getValues(dimensions: readonly DimensionName[], idx: number): ParsedValue[];
    getValues(dimensions: readonly DimensionName[] | number, idx?: number): ParsedValue[] {
773
        const values = [];
774 775 776

        if (!zrUtil.isArray(dimensions)) {
            // stack = idx;
777
            idx = dimensions as number;
778 779
            dimensions = this.dimensions;
        }
P
pissang 已提交
780

781
        for (let i = 0, len = dimensions.length; i < len; i++) {
782 783
            values.push(this.get(dimensions[i], idx /*, stack */));
        }
S
sushuang 已提交
784

785
        return values;
S
sushuang 已提交
786
    }
787 788 789 790 791 792

    /**
     * If value is NaN. Inlcuding '-'
     * Only check the coord dimensions.
     */
    hasValue(idx: number): boolean {
793
        const dataDimsOnCoord = this._dimensionsSummary.dataDimsOnCoord;
794
        for (let i = 0, len = dataDimsOnCoord.length; i < len; i++) {
795 796 797 798 799 800 801 802
            // Ordinal type originally can be string or number.
            // But when an ordinal type is used on coord, it can
            // not be string but only number. So we can also use isNaN.
            if (isNaN(this.get(dataDimsOnCoord[i], idx) as any)) {
                return false;
            }
        }
        return true;
S
sushuang 已提交
803 804
    }

805 806 807 808 809 810
    /**
     * Get extent of data in one dimension
     */
    getDataExtent(dim: DimensionLoose): [number, number] {
        // Make sure use concrete dim as cache name.
        dim = this.getDimension(dim);
811 812
        const dimData = this._storage[dim];
        const initialExtent = getInitialExtent();
S
sushuang 已提交
813

814
        // stack = !!((stack || false) && this.getCalculationInfo(dim));
P
pissang 已提交
815

816 817 818
        if (!dimData) {
            return initialExtent;
        }
S
sushuang 已提交
819

820
        // Make more strict checkings to ensure hitting cache.
821
        const currEnd = this.count();
822 823
        // let cacheName = [dim, !!stack].join('_');
        // let cacheName = dim;
S
sushuang 已提交
824

825 826 827
        // Consider the most cases when using data zoom, `getDataExtent`
        // happened before filtering. We cache raw extent, which is not
        // necessary to be cleared and recalculated when restore data.
828
        const useRaw = !this._indices; // && !stack;
829
        let dimExtent: [number, number];
S
sushuang 已提交
830

831 832 833 834 835 836 837 838
        if (useRaw) {
            return this._rawExtent[dim].slice() as [number, number];
        }
        dimExtent = this._extent[dim];
        if (dimExtent) {
            return dimExtent.slice() as [number, number];
        }
        dimExtent = initialExtent;
S
sushuang 已提交
839

840 841
        let min = dimExtent[0];
        let max = dimExtent[1];
842

843
        for (let i = 0; i < currEnd; i++) {
P
pissang 已提交
844
            const rawIdx = this.getRawIndex(i);
P
pissang 已提交
845
            const value = dimData[rawIdx] as ParsedValueNumeric;
846 847
            value < min && (min = value);
            value > max && (max = value);
L
lang 已提交
848
        }
L
lang 已提交
849

850
        dimExtent = [min, max];
S
sushuang 已提交
851

852
        this._extent[dim] = dimExtent;
P
pissang 已提交
853

854
        return dimExtent;
S
sushuang 已提交
855
    }
S
sushuang 已提交
856

857
    /**
858 859 860 861 862 863 864 865
     * PENDING: In fact currently this function is only used to short-circuit
     * the calling of `scale.unionExtentFromData` when data have been filtered by modules
     * like "dataZoom". `scale.unionExtentFromData` is used to calculate data extent for series on
     * an axis, but if a "axis related data filter module" is used, the extent of the axis have
     * been fixed and no need to calling `scale.unionExtentFromData` actually.
     * But if we add "custom data filter" in future, which is not "axis related", this method may
     * be still needed.
     *
866 867 868 869 870 871 872
     * Optimize for the scenario that data is filtered by a given extent.
     * Consider that if data amount is more than hundreds of thousand,
     * extent calculation will cost more than 10ms and the cache will
     * be erased because of the filtering.
     */
    getApproximateExtent(dim: DimensionLoose): [number, number] {
        dim = this.getDimension(dim);
873
        return this._approximateExtent[dim] || this.getDataExtent(dim);
874
    }
S
sushuang 已提交
875

876 877 878 879
    /**
     * Calculate extent on a filtered data might be time consuming.
     * Approximate extent is only used for: calculte extent of filtered data outside.
     */
880 881 882
    setApproximateExtent(extent: [number, number], dim: DimensionLoose): void {
        dim = this.getDimension(dim);
        this._approximateExtent[dim] = extent.slice() as [number, number];
S
sushuang 已提交
883
    }
884

885 886 887
    getCalculationInfo<CALC_INFO_KEY extends keyof DataCalculationInfo<HostModel>>(
        key: CALC_INFO_KEY
    ): DataCalculationInfo<HostModel>[CALC_INFO_KEY] {
888
        return this._calculationInfo[key];
S
sushuang 已提交
889 890
    }

891 892 893
    /**
     * @param key or k-v object
     */
894 895 896 897 898 899 900 901 902 903 904
    setCalculationInfo(
        key: DataCalculationInfo<HostModel>
    ): void;
    setCalculationInfo<CALC_INFO_KEY extends keyof DataCalculationInfo<HostModel>>(
        key: CALC_INFO_KEY,
        value: DataCalculationInfo<HostModel>[CALC_INFO_KEY]
    ): void;
    setCalculationInfo(
        key: (keyof DataCalculationInfo<HostModel>) | DataCalculationInfo<HostModel>,
        value?: DataCalculationInfo<HostModel>[keyof DataCalculationInfo<HostModel>]
    ): void {
905 906
        isObject(key)
            ? zrUtil.extend(this._calculationInfo, key as object)
907
            : ((this._calculationInfo as any)[key] = value);
908
    }
S
sushuang 已提交
909

910 911 912 913
    /**
     * Get sum of data in one dimension
     */
    getSum(dim: DimensionName): number {
914
        const dimData = this._storage[dim];
915
        let sum = 0;
916
        if (dimData) {
917
            for (let i = 0, len = this.count(); i < len; i++) {
918
                const value = this.get(dim, i) as number;
919 920 921 922 923 924
                if (!isNaN(value)) {
                    sum += value;
                }
            }
        }
        return sum;
S
sushuang 已提交
925
    }
S
sushuang 已提交
926

927 928 929 930
    /**
     * Get median of data in one dimension
     */
    getMedian(dim: DimensionLoose): number {
931
        const dimDataArray: ParsedValue[] = [];
932 933 934 935 936 937 938 939 940
        // map all data of one dimension
        this.each(dim, function (val) {
            if (!isNaN(val as number)) {
                dimDataArray.push(val);
            }
        });

        // TODO
        // Use quick select?
941
        const sortedDimDataArray = dimDataArray.sort(function (a: number, b: number) {
942
            return a - b;
943
        }) as number[];
944
        const len = this.count();
945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962
        // calculate median
        return len === 0
            ? 0
            : len % 2 === 1
            ? sortedDimDataArray[(len - 1) / 2]
            : (sortedDimDataArray[len / 2] + sortedDimDataArray[len / 2 - 1]) / 2;
    }

    // /**
    //  * Retreive the index with given value
    //  * @param {string} dim Concrete dimension.
    //  * @param {number} value
    //  * @return {number}
    //  */
    // Currently incorrect: should return dataIndex but not rawIndex.
    // Do not fix it until this method is to be used somewhere.
    // FIXME Precision of float value
    // indexOf(dim, value) {
963 964 965
    //     let storage = this._storage;
    //     let dimData = storage[dim];
    //     let chunkSize = this._chunkSize;
966
    //     if (dimData) {
967
    //         for (let i = 0, len = this.count(); i < len; i++) {
968
    //             let chunkIndex = mathFloor(i / chunkSize);
969
    //             let chunkOffset = i % chunkSize;
970 971 972 973 974 975 976
    //             if (dimData[chunkIndex][chunkOffset] === value) {
    //                 return i;
    //             }
    //         }
    //     }
    //     return -1;
    // }
S
sushuang 已提交
977

978 979 980 981 982 983 984
    /**
     * Only support the dimension which inverted index created.
     * Do not support other cases until required.
     * @param dim concrete dim
     * @param value ordinal index
     * @return rawIndex
     */
1
100pah 已提交
985
    rawIndexOf(dim: DimensionName, value: OrdinalNumber): number {
986
        const invertedIndices = dim && this._invertedIndicesMap[dim];
987 988 989 990 991
        if (__DEV__) {
            if (!invertedIndices) {
                throw new Error('Do not supported yet');
            }
        }
992
        const rawIndex = invertedIndices[value];
993 994 995 996 997
        if (rawIndex == null || isNaN(rawIndex)) {
            return INDEX_NOT_FOUND;
        }
        return rawIndex;
    }
S
sushuang 已提交
998

999 1000 1001 1002
    /**
     * Retreive the index with given name
     */
    indexOfName(name: string): number {
1003
        for (let i = 0, len = this.count(); i < len; i++) {
1004 1005
            if (this.getName(i) === name) {
                return i;
L
lang 已提交
1006 1007
            }
        }
H
hustcc 已提交
1008

1009 1010
        return -1;
    }
S
sushuang 已提交
1011

1012 1013 1014 1015 1016 1017
    /**
     * Retreive the index with given raw data index
     */
    indexOfRawIndex(rawIndex: number): number {
        if (rawIndex >= this._rawCount || rawIndex < 0) {
            return -1;
L
lang 已提交
1018 1019
        }

1020 1021
        if (!this._indices) {
            return rawIndex;
1022 1023
        }

1024
        // Indices are ascending
1025
        const indices = this._indices;
L
lang 已提交
1026

1027
        // If rawIndex === dataIndex
1028
        const rawDataIndex = indices[rawIndex];
1029 1030 1031 1032
        if (rawDataIndex != null && rawDataIndex < this._count && rawDataIndex === rawIndex) {
            return rawIndex;
        }

1033 1034
        let left = 0;
        let right = this._count - 1;
1035
        while (left <= right) {
1036
            const mid = (left + right) / 2 | 0;
1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
            if (indices[mid] < rawIndex) {
                left = mid + 1;
            }
            else if (indices[mid] > rawIndex) {
                right = mid - 1;
            }
            else {
                return mid;
            }
        }
1047 1048 1049
        return -1;
    }

1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
    /**
     * Retreive the index of nearest value
     * @param dim
     * @param value
     * @param [maxDistance=Infinity]
     * @return If and only if multiple indices has
     *         the same value, they are put to the result.
     */
    indicesOfNearest(
        dim: DimensionName, value: number, maxDistance?: number
    ): number[] {
1061 1062 1063
        const storage = this._storage;
        const dimData = storage[dim];
        const nearestIndices: number[] = [];
1064 1065 1066 1067

        if (!dimData) {
            return nearestIndices;
        }
1068

1069 1070 1071
        if (maxDistance == null) {
            maxDistance = Infinity;
        }
S
sushuang 已提交
1072

1073 1074 1075
        let minDist = Infinity;
        let minDiff = -1;
        let nearestIndicesLen = 0;
1076

1077

1078
        // Check the test case of `test/ut/spec/data/List.js`.
1079
        for (let i = 0, len = this.count(); i < len; i++) {
1080
            const dataIndex = this.getRawIndex(i);
P
pissang 已提交
1081
            const diff = value - (dimData[dataIndex] as number);
1082
            const dist = Math.abs(diff);
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100
            if (dist <= maxDistance) {
                // When the `value` is at the middle of `this.get(dim, i)` and `this.get(dim, i+1)`,
                // we'd better not push both of them to `nearestIndices`, otherwise it is easy to
                // get more than one item in `nearestIndices` (more specifically, in `tooltip`).
                // So we chose the one that `diff >= 0` in this csae.
                // But if `this.get(dim, i)` and `this.get(dim, j)` get the same value, both of them
                // should be push to `nearestIndices`.
                if (dist < minDist
                    || (dist === minDist && diff >= 0 && minDiff < 0)
                ) {
                    minDist = dist;
                    minDiff = diff;
                    nearestIndicesLen = 0;
                }
                if (diff === minDiff) {
                    nearestIndices[nearestIndicesLen++] = i;
                }
            }
L
lang 已提交
1101
        }
1102
        nearestIndices.length = nearestIndicesLen;
L
lang 已提交
1103

S
sushuang 已提交
1104 1105
        return nearestIndices;
    }
L
lang 已提交
1106

1107 1108 1109 1110 1111 1112
    /**
     * Get raw data index.
     * Do not initialize.
     * Default `getRawIndex`. And it can be changed.
     */
    getRawIndex: (idx: number) => number = getRawIndexWithoutIndices;
L
lang 已提交
1113

1114 1115 1116 1117 1118
    /**
     * Get raw data item
     */
    getRawDataItem(idx: number): OptionDataItem {
        if (!this._rawData.persistent) {
1119
            const val = [];
1120
            for (let i = 0; i < this.dimensions.length; i++) {
1121
                const dim = this.dimensions[i];
1122
                val.push(this.get(dim, idx));
S
sushuang 已提交
1123
            }
1124 1125 1126 1127
            return val;
        }
        else {
            return this._rawData.getItem(this.getRawIndex(idx));
L
lang 已提交
1128 1129
        }
    }
P
pissang 已提交
1130

1131 1132 1133 1134 1135 1136
    /**
     * @return Never be null/undefined. `number` will be converted to string. Becuase:
     * In most cases, name is used in display, where returning a string is more convenient.
     * In other cases, name is used in query (see `indexOfName`), where we can keep the
     * rule that name `2` equals to name `'2'`.
     */
1137
    getName(idx: number): string {
1138
        const rawIndex = this.getRawIndex(idx);
1139
        return this._nameList[rawIndex]
1140
            || convertOptionIdName(getRawValueFromStore(this, this._nameDimIdx, rawIndex), '')
1141 1142
            || '';
    }
P
pissang 已提交
1143

1144 1145 1146 1147 1148 1149
    /**
     * @return Never null/undefined. `number` will be converted to string. Becuase:
     * In all cases having encountered at present, id is used in making diff comparison, which
     * are usually based on hash map. We can keep the rule that the internal id are always string
     * (treat `2` is the same as `'2'`) to make the related logic simple.
     */
1150 1151
    getId(idx: number): string {
        return getId(this, this.getRawIndex(idx));
1152
    }
L
lang 已提交
1153

1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176
    /**
     * Data iteration
     * @param ctx default this
     * @example
     *  list.each('x', function (x, idx) {});
     *  list.each(['x', 'y'], function (x, y, idx) {});
     *  list.each(function (idx) {})
     */
    each<Ctx>(cb: EachCb0<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
    each<Ctx>(dims: DimensionLoose, cb: EachCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
    each<Ctx>(dims: [DimensionLoose], cb: EachCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
    each<Ctx>(dims: [DimensionLoose, DimensionLoose], cb: EachCb2<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
    each<Ctx>(dims: ItrParamDims, cb: EachCb<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
    each<Ctx>(
        dims: ItrParamDims | EachCb<Ctx>,
        cb: EachCb<Ctx> | Ctx,
        ctx?: Ctx,
        ctxCompat?: Ctx
    ): void {
        'use strict';

        if (!this._count) {
            return;
1177
        }
L
lang 已提交
1178

1179 1180 1181 1182 1183 1184
        if (typeof dims === 'function') {
            ctxCompat = ctx;
            ctx = cb as Ctx;
            cb = dims;
            dims = [];
        }
L
lang 已提交
1185

1186
        // ctxCompat just for compat echarts3
1187
        const fCtx = (ctx || ctxCompat || this) as CtxOrList<Ctx>;
L
lang 已提交
1188

P
pissang 已提交
1189
        const dimNames = map(normalizeDimensions(dims), this.getDimension, this);
L
lang 已提交
1190

1191 1192 1193
        if (__DEV__) {
            validateDimensions(this, dimNames);
        }
L
lang 已提交
1194

1195
        const dimSize = dimNames.length;
P
pissang 已提交
1196
        const dimIndices = map(dimNames, (dimName) => {
P
pissang 已提交
1197 1198
            return this._dimensionInfos[dimName].index;
        });
P
pissang 已提交
1199
        const storageArr = this._storageArr;
1200

P
pissang 已提交
1201
        for (let i = 0, len = this.count(); i < len; i++) {
1202 1203 1204 1205 1206 1207
            // Simple optimization
            switch (dimSize) {
                case 0:
                    (cb as EachCb0<Ctx>).call(fCtx, i);
                    break;
                case 1:
P
pissang 已提交
1208
                    (cb as EachCb1<Ctx>).call(fCtx, storageArr[dimIndices[0]][i], i);
1209 1210
                    break;
                case 2:
P
pissang 已提交
1211
                    (cb as EachCb2<Ctx>).call(
P
pissang 已提交
1212
                        fCtx, storageArr[dimIndices[0]][i], storageArr[dimIndices[1]][i], i
P
pissang 已提交
1213
                    );
1214 1215
                    break;
                default:
1216
                    let k = 0;
1217
                    const value = [];
1218
                    for (; k < dimSize; k++) {
P
pissang 已提交
1219
                        value[k] = storageArr[dimIndices[k]][i];
1220 1221 1222 1223 1224
                    }
                    // Index
                    value[k] = i;
                    (cb as EachCb<Ctx>).apply(fCtx, value);
            }
1225 1226 1227
        }
    }

1228 1229 1230
    /**
     * Data filter
     */
P
pissang 已提交
1231 1232 1233 1234 1235
    filterSelf<Ctx>(cb: FilterCb0<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): this;
    filterSelf<Ctx>(dims: DimensionLoose, cb: FilterCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): this;
    filterSelf<Ctx>(dims: [DimensionLoose], cb: FilterCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): this;
    filterSelf<Ctx>(dims: [DimensionLoose, DimensionLoose], cb: FilterCb2<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): this;
    filterSelf<Ctx>(dims: ItrParamDims, cb: FilterCb<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): this;
1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246
    filterSelf<Ctx>(
        dims: ItrParamDims | FilterCb<Ctx>,
        cb: FilterCb<Ctx> | Ctx,
        ctx?: Ctx,
        ctxCompat?: Ctx
    ): List {
        'use strict';

        if (!this._count) {
            return;
        }
1247

1248 1249 1250 1251 1252 1253
        if (typeof dims === 'function') {
            ctxCompat = ctx;
            ctx = cb as Ctx;
            cb = dims;
            dims = [];
        }
P
pissang 已提交
1254

1255
        // ctxCompat just for compat echarts3
1256
        const fCtx = (ctx || ctxCompat || this) as CtxOrList<Ctx>;
S
sushuang 已提交
1257

P
pissang 已提交
1258
        const dimNames = map(
1259 1260
            normalizeDimensions(dims), this.getDimension, this
        );
S
sushuang 已提交
1261

1262 1263 1264
        if (__DEV__) {
            validateDimensions(this, dimNames);
        }
S
sushuang 已提交
1265

1266

1267 1268 1269 1270 1271
        const count = this.count();
        const Ctor = getIndicesCtor(this);
        const newIndices = new Ctor(count);
        const value = [];
        const dimSize = dimNames.length;
1272

1273
        let offset = 0;
P
pissang 已提交
1274
        const dimIndices = map(dimNames, (dimName) => {
P
pissang 已提交
1275 1276 1277
            return this._dimensionInfos[dimName].index;
        });
        const dim0 = dimIndices[0];
P
pissang 已提交
1278
        const storageArr = this._storageArr;
1279

1280 1281
        for (let i = 0; i < count; i++) {
            let keep;
1282
            const rawIdx = this.getRawIndex(i);
1283 1284 1285 1286 1287
            // Simple optimization
            if (dimSize === 0) {
                keep = (cb as FilterCb0<Ctx>).call(fCtx, i);
            }
            else if (dimSize === 1) {
P
pissang 已提交
1288
                const val = storageArr[dim0][rawIdx];
1289 1290 1291
                keep = (cb as FilterCb1<Ctx>).call(fCtx, val, i);
            }
            else {
1
100pah 已提交
1292 1293
                let k = 0;
                for (; k < dimSize; k++) {
P
pissang 已提交
1294
                    value[k] = storageArr[dimIndices[k]][rawIdx];
L
lang 已提交
1295 1296
                }
                value[k] = i;
1297 1298 1299 1300 1301
                keep = (cb as FilterCb<Ctx>).apply(fCtx, value);
            }
            if (keep) {
                newIndices[offset++] = rawIdx;
            }
L
lang 已提交
1302
        }
P
pissang 已提交
1303

1304 1305 1306 1307 1308 1309 1310
        // Set indices after filtered.
        if (offset < count) {
            this._indices = newIndices;
        }
        this._count = offset;
        // Reset data extent
        this._extent = {};
P
pissang 已提交
1311

1312
        this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
S
tweak.  
sushuang 已提交
1313

1314
        return this;
1315 1316
    }

1317 1318 1319 1320
    /**
     * Select data in range. (For optimization of filter)
     * (Manually inline code, support 5 million data filtering in data zoom.)
     */
P
pissang 已提交
1321
    selectRange(range: {[dimName: string]: [number, number]}): List {
1322
        'use strict';
1323

P
pissang 已提交
1324 1325 1326
        const len = this._count;

        if (!len) {
1327
            return;
S
tweak.  
sushuang 已提交
1328
        }
1329

1330 1331
        const dimensions = [];
        for (const dim in range) {
1332 1333
            if (range.hasOwnProperty(dim)) {
                dimensions.push(dim);
S
tweak.  
sushuang 已提交
1334 1335
            }
        }
P
pissang 已提交
1336

1337 1338
        if (__DEV__) {
            validateDimensions(this, dimensions);
S
sushuang 已提交
1339
        }
1340

1341
        const dimSize = dimensions.length;
1342 1343 1344
        if (!dimSize) {
            return;
        }
P
pissang 已提交
1345

1346 1347 1348
        const originalCount = this.count();
        const Ctor = getIndicesCtor(this);
        const newIndices = new Ctor(originalCount);
1349

1350
        let offset = 0;
1351
        const dim0 = dimensions[0];
P
pissang 已提交
1352
        const dimIndices = map(dimensions, (dimName) => {
P
pissang 已提交
1353 1354
            return this._dimensionInfos[dimName].index;
        });
1355

1356 1357
        const min = range[dim0][0];
        const max = range[dim0][1];
P
pissang 已提交
1358
        const storageArr = this._storageArr;
1359

1360
        let quickFinished = false;
1361 1362
        if (!this._indices) {
            // Extreme optimization for common case. About 2x faster in chrome.
1363
            let idx = 0;
1364
            if (dimSize === 1) {
P
pissang 已提交
1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376
                const dimStorage = storageArr[dimIndices[0]];
                for (let i = 0; i < len; i++) {
                    const val = dimStorage[i];
                    // NaN will not be filtered. Consider the case, in line chart, empty
                    // value indicates the line should be broken. But for the case like
                    // scatter plot, a data item with empty value will not be rendered,
                    // but the axis extent may be effected if some other dim of the data
                    // item has value. Fortunately it is not a significant negative effect.
                    if (
                        (val >= min && val <= max) || isNaN(val as any)
                    ) {
                        newIndices[offset++] = idx;
1377
                    }
P
pissang 已提交
1378
                    idx++;
1379
                }
1380
                quickFinished = true;
P
pissang 已提交
1381
            }
1382
            else if (dimSize === 2) {
P
pissang 已提交
1383 1384
                const dimStorage = storageArr[dimIndices[0]];
                const dimStorage2 = storageArr[dimIndices[1]];
1385 1386
                const min2 = range[dimensions[1]][0];
                const max2 = range[dimensions[1]][1];
P
pissang 已提交
1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398
                for (let i = 0; i < len; i++) {
                    const val = dimStorage[i];
                    const val2 = dimStorage2[i];
                    // Do not filter NaN, see comment above.
                    if ((
                            (val >= min && val <= max) || isNaN(val as any)
                        )
                        && (
                            (val2 >= min2 && val2 <= max2) || isNaN(val2 as any)
                        )
                    ) {
                        newIndices[offset++] = idx;
P
pissang 已提交
1399
                    }
P
pissang 已提交
1400
                    idx++;
P
pissang 已提交
1401
                }
1402
                quickFinished = true;
P
pissang 已提交
1403 1404
            }
        }
1405 1406
        if (!quickFinished) {
            if (dimSize === 1) {
1
100pah 已提交
1407
                for (let i = 0; i < originalCount; i++) {
1408
                    const rawIndex = this.getRawIndex(i);
P
pissang 已提交
1409
                    const val = storageArr[dimIndices[0]][rawIndex];
1410
                    // Do not filter NaN, see comment above.
1411 1412 1413 1414
                    if (
                        (val >= min && val <= max) || isNaN(val as any)
                    ) {
                        newIndices[offset++] = rawIndex;
P
pissang 已提交
1415 1416
                    }
                }
1417 1418
            }
            else {
1
100pah 已提交
1419 1420
                for (let i = 0; i < originalCount; i++) {
                    let keep = true;
1421
                    const rawIndex = this.getRawIndex(i);
1
100pah 已提交
1422
                    for (let k = 0; k < dimSize; k++) {
1423
                        const dimk = dimensions[k];
P
pissang 已提交
1424
                        const val = storageArr[dimIndices[k]][rawIndex];
1425 1426 1427 1428 1429 1430 1431 1432
                        // Do not filter NaN, see comment above.
                        if (val < range[dimk][0] || val > range[dimk][1]) {
                            keep = false;
                        }
                    }
                    if (keep) {
                        newIndices[offset++] = this.getRawIndex(i);
                    }
P
pissang 已提交
1433 1434 1435 1436
                }
            }
        }

1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447
        // Set indices after filtered.
        if (offset < originalCount) {
            this._indices = newIndices;
        }
        this._count = offset;
        // Reset data extent
        this._extent = {};

        this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;

        return this;
P
pissang 已提交
1448 1449
    }

1450 1451 1452
    /**
     * Data mapping to a plain array
     */
P
pissang 已提交
1453
    mapArray<Ctx, Cb extends MapArrayCb0<Ctx>>(cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType<Cb>[];
1454
    /* eslint-disable */
P
pissang 已提交
1455 1456 1457 1458
    mapArray<Ctx, Cb extends MapArrayCb1<Ctx>>(dims: DimensionLoose, cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType<Cb>[];
    mapArray<Ctx, Cb extends MapArrayCb1<Ctx>>(dims: [DimensionLoose], cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType<Cb>[];
    mapArray<Ctx, Cb extends MapArrayCb2<Ctx>>(dims: [DimensionLoose, DimensionLoose], cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType<Cb>[];
    mapArray<Ctx, Cb extends MapArrayCb<Ctx>>(dims: ItrParamDims, cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType<Cb>[];
1459
    /* eslint-enable */
1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473
    mapArray<Ctx>(
        dims: ItrParamDims | MapArrayCb<Ctx>,
        cb: MapArrayCb<Ctx> | Ctx,
        ctx?: Ctx,
        ctxCompat?: Ctx
    ): any[] {
        'use strict';

        if (typeof dims === 'function') {
            ctxCompat = ctx;
            ctx = cb as Ctx;
            cb = dims;
            dims = [];
        }
P
pissang 已提交
1474

1475 1476
        // ctxCompat just for compat echarts3
        ctx = (ctx || ctxCompat || this) as Ctx;
P
pissang 已提交
1477

1478
        const result: any[] = [];
1479 1480 1481 1482
        this.each(dims, function () {
            result.push(cb && (cb as MapArrayCb<Ctx>).apply(this, arguments));
        }, ctx);
        return result;
S
sushuang 已提交
1483 1484
    }

1485 1486 1487
    /**
     * Data mapping to a new List with given dimensions
     */
P
pissang 已提交
1488 1489 1490
    map<Ctx>(dims: DimensionLoose, cb: MapCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List<HostModel>;
    map<Ctx>(dims: [DimensionLoose], cb: MapCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List<HostModel>;
    map<Ctx>(dims: [DimensionLoose, DimensionLoose], cb: MapCb2<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List<HostModel>;
1491 1492 1493 1494 1495 1496 1497 1498 1499
    map<Ctx>(
        dims: ItrParamDims,
        cb: MapCb<Ctx>,
        ctx?: Ctx,
        ctxCompat?: Ctx
    ): List {
        'use strict';

        // ctxCompat just for compat echarts3
1500
        const fCtx = (ctx || ctxCompat || this) as CtxOrList<Ctx>;
1501

P
pissang 已提交
1502
        const dimNames = map(
1503 1504 1505 1506 1507 1508 1509
            normalizeDimensions(dims), this.getDimension, this
        );

        if (__DEV__) {
            validateDimensions(this, dimNames);
        }

1510
        const list = cloneListForMapAndSample(this, dimNames);
P
pissang 已提交
1511
        const storage = list._storage;
S
sushuang 已提交
1512

1513 1514 1515 1516 1517
        // Following properties are all immutable.
        // So we can reference to the same value
        list._indices = this._indices;
        list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;

1518 1519 1520 1521 1522
        const tmpRetValue = [];
        const dimSize = dimNames.length;
        const dataCount = this.count();
        const values = [];
        const rawExtent = list._rawExtent;
1523

1524 1525
        for (let dataIndex = 0; dataIndex < dataCount; dataIndex++) {
            for (let dimIndex = 0; dimIndex < dimSize; dimIndex++) {
1526
                values[dimIndex] = this.get(dimNames[dimIndex], dataIndex);
S
sushuang 已提交
1527
            }
1528 1529
            values[dimSize] = dataIndex;

1530
            let retValue = cb && cb.apply(fCtx, values);
1531 1532 1533 1534 1535 1536 1537
            if (retValue != null) {
                // a number or string (in oridinal dimension)?
                if (typeof retValue !== 'object') {
                    tmpRetValue[0] = retValue;
                    retValue = tmpRetValue;
                }

1538
                const rawIndex = this.getRawIndex(dataIndex);
1539

1540
                for (let i = 0; i < retValue.length; i++) {
1541 1542 1543
                    const dim = dimNames[i];
                    const val = retValue[i];
                    const rawExtentOnDim = rawExtent[dim];
1544

1545
                    const dimStore = storage[dim];
1546
                    if (dimStore) {
P
pissang 已提交
1547
                        dimStore[rawIndex] = val;
1548 1549 1550 1551 1552 1553 1554 1555 1556
                    }

                    if (val < rawExtentOnDim[0]) {
                        rawExtentOnDim[0] = val as number;
                    }
                    if (val > rawExtentOnDim[1]) {
                        rawExtentOnDim[1] = val as number;
                    }
                }
S
sushuang 已提交
1557
            }
S
sushuang 已提交
1558
        }
S
sushuang 已提交
1559

1560
        return list;
S
sushuang 已提交
1561 1562
    }

1563 1564 1565 1566 1567 1568 1569
    /**
     * Large data down sampling on given dimension
     * @param sampleIndex Sample index for name and id
     */
    downSample(
        dimension: DimensionName,
        rate: number,
1570 1571
        sampleValue: (frameValues: ArrayLike<ParsedValue>) => ParsedValueNumeric,
        sampleIndex: (frameValues: ArrayLike<ParsedValue>, value: ParsedValueNumeric) => number
P
pissang 已提交
1572
    ): List<HostModel> {
1573 1574
        const list = cloneListForMapAndSample(this, [dimension]);
        const targetStorage = list._storage;
1575

1576
        const frameValues = [];
1577
        let frameSize = mathFloor(1 / rate);
1578

1579 1580 1581
        const dimStore = targetStorage[dimension];
        const len = this.count();
        const rawExtentOnDim = list._rawExtent[dimension];
1582

1583
        const newIndices = new (getIndicesCtor(this))(len);
1584

1585 1586
        let offset = 0;
        for (let i = 0; i < len; i += frameSize) {
1587 1588 1589 1590 1591
            // Last frame
            if (frameSize > len - i) {
                frameSize = len - i;
                frameValues.length = frameSize;
            }
1592
            for (let k = 0; k < frameSize; k++) {
1593
                const dataIdx = this.getRawIndex(i + k);
P
pissang 已提交
1594
                frameValues[k] = dimStore[dataIdx];
1595
            }
1596 1597
            const value = sampleValue(frameValues);
            const sampleFrameIdx = this.getRawIndex(
1598 1599 1600
                Math.min(i + sampleIndex(frameValues, value) || 0, len - 1)
            );
            // Only write value on the filtered data
P
pissang 已提交
1601
            dimStore[sampleFrameIdx] = value;
S
sushuang 已提交
1602

1603 1604 1605 1606 1607 1608 1609 1610 1611
            if (value < rawExtentOnDim[0]) {
                rawExtentOnDim[0] = value;
            }
            if (value > rawExtentOnDim[1]) {
                rawExtentOnDim[1] = value;
            }

            newIndices[offset++] = sampleFrameIdx;
        }
1612

1613 1614
        list._count = offset;
        list._indices = newIndices;
S
sushuang 已提交
1615

1616
        list.getRawIndex = getRawIndexWithIndices;
S
sushuang 已提交
1617

P
pissang 已提交
1618
        return list as List<HostModel>;
1619 1620
    }

1621 1622 1623
    /**
     * Large data down sampling using largest-triangle-three-buckets
     * @param {string} valueDimension
1624
     * @param {number} targetCount
1625 1626
     */
    lttbDownSample(
1627
        valueDimension: DimensionName,
1628
        rate: number
1629
    ) {
P
pissang 已提交
1630
        const list = cloneListForMapAndSample(this, []);
1631
        const targetStorage = list._storage;
1632
        const dimStore = targetStorage[valueDimension];
1633 1634 1635 1636 1637
        const len = this.count();
        const newIndices = new (getIndicesCtor(this))(len);

        let sampledIndex = 0;

1638
        const frameSize = mathFloor(1 / rate);
1639

1640
        let currentRawIndex = this.getRawIndex(0);
1641 1642
        let maxArea;
        let area;
1643 1644
        let nextRawIndex;

1645
        // First frame use the first data.
1646
        newIndices[sampledIndex++] = currentRawIndex;
1647 1648 1649
        for (let i = 1; i < len - 1; i += frameSize) {
            const nextFrameStart = Math.min(i + frameSize, len - 1);
            const nextFrameEnd = Math.min(i + frameSize * 2, len);
1650

1651 1652
            const avgX = (nextFrameEnd + nextFrameStart) / 2;
            let avgY = 0;
1653

1654
            for (let idx = nextFrameStart; idx < nextFrameEnd; idx++) {
1655
                const rawIndex = this.getRawIndex(idx);
1656 1657
                const y = dimStore[rawIndex] as number;
                if (isNaN(y)) {
1658
                    continue;
1659
                }
P
pissang 已提交
1660
                avgY += y as number;
1661
            }
1662
            avgY /= (nextFrameEnd - nextFrameStart);
1663

1664 1665
            const frameStart = i;
            const frameEnd = Math.min(i + frameSize, len);
1666

1667 1668
            const pointAX = i - 1;
            const pointAY = dimStore[currentRawIndex] as number;
1669

P
pissang 已提交
1670
            maxArea = -1;
1671

1672
            nextRawIndex = frameStart;
1673 1674
            // Find a point from current frame that construct a triangel with largest area with previous selected point
            // And the average of next frame.
1675
            for (let idx = frameStart; idx < frameEnd; idx++) {
1676
                const rawIndex = this.getRawIndex(idx);
1677 1678
                const y = dimStore[rawIndex] as number;
                if (isNaN(y)) {
1679
                    continue;
1680
                }
1681 1682
                // Calculate triangle area over three buckets
                area = Math.abs((pointAX - avgX) * (y - pointAY)
1683
                    - (pointAX - idx) * (avgY - pointAY)
1684 1685 1686 1687
                );
                if (area > maxArea) {
                    maxArea = area;
                    nextRawIndex = rawIndex; // Next a is this b
1688 1689
                }
            }
1690 1691 1692 1693

            newIndices[sampledIndex++] = nextRawIndex;

            currentRawIndex = nextRawIndex; // This a is the next a (chosen b)
1694 1695
        }

1696
        // First frame use the last data.
1697
        newIndices[sampledIndex++] = this.getRawIndex(len - 1);
1698 1699 1700 1701 1702 1703 1704 1705
        list._count = sampledIndex;
        list._indices = newIndices;

        list.getRawIndex = getRawIndexWithIndices;
        return list;
    }


1706 1707 1708
    /**
     * Get model of one data item.
     */
P
pissang 已提交
1709
    // TODO: Type of data item
P
pissang 已提交
1710 1711 1712 1713
    getItemModel<ItemOpts extends unknown = unknown>(idx: number): Model<ItemOpts
        // Extract item option with value key. FIXME will cause incompatitable issue
        // Extract<HostModel['option']['data'][number], { value?: any }>
    > {
1714 1715
        const hostModel = this.hostModel;
        const dataItem = this.getRawDataItem(idx) as ModelOption;
1716
        return new Model(dataItem, hostModel, hostModel && hostModel.ecModel);
1717
    }
S
sushuang 已提交
1718

1719 1720 1721 1722
    /**
     * Create a data differ
     */
    diff(otherList: List): DataDiffer {
1723
        const thisList = this;
1724 1725 1726 1727

        return new DataDiffer(
            otherList ? otherList.getIndices() : [],
            this.getIndices(),
1
100pah 已提交
1728
            function (idx: number) {
1729 1730
                return getId(otherList, idx);
            },
1
100pah 已提交
1731
            function (idx: number) {
1732 1733 1734 1735
                return getId(thisList, idx);
            }
        );
    }
S
sushuang 已提交
1736

1737 1738 1739
    /**
     * Get visual property.
     */
1740 1741
    getVisual<K extends keyof Visual>(key: K): Visual[K] {
        const visual = this._visual as Visual;
1742 1743
        return visual && visual[key];
    }
S
sushuang 已提交
1744

1745 1746 1747 1748 1749 1750 1751 1752 1753
    /**
     * Set visual property
     *
     * @example
     *  setVisual('color', color);
     *  setVisual({
     *      'color': color
     *  });
     */
1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797
    setVisual<K extends keyof Visual>(key: K, val: Visual[K]): void;
    setVisual(kvObj: Partial<Visual>): void;
    setVisual(kvObj: string | Partial<Visual>, val?: any): void {
        this._visual = this._visual || {};
        if (isObject(kvObj)) {
            zrUtil.extend(this._visual, kvObj);
        }
        else {
            this._visual[kvObj as string] = val;
        }
    }

    /**
     * Get visual property of single data item
     */
    // eslint-disable-next-line
    getItemVisual<K extends keyof Visual>(idx: number, key: K): Visual[K] {
        const itemVisual = this._itemVisuals[idx] as Visual;
        const val = itemVisual && itemVisual[key];
        if (val == null) {
            // Use global visual property
            return this.getVisual(key);
        }
        return val;
    }

    /**
     * Make sure itemVisual property is unique
     */
    // TODO: use key to save visual to reduce memory.
    // eslint-disable-next-line
    ensureUniqueItemVisual<K extends keyof Visual>(idx: number, key: K): Visual[K] {
        const itemVisuals = this._itemVisuals;
        let itemVisual = itemVisuals[idx] as Visual;
        if (!itemVisual) {
            itemVisual = itemVisuals[idx] = {} as Visual;
        }
        let val = itemVisual[key];
        if (!val) {
            val = this.getVisual(key);

            // TODO Performance?
            if (zrUtil.isArray(val)) {
                val = val.slice() as unknown as Visual[K];
1798
            }
1799 1800 1801 1802 1803
            else if (isObject(val)) {
                val = zrUtil.extend({}, val);
            }

            itemVisual[key] = val;
S
sushuang 已提交
1804
        }
1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841
        return val;
    }
    /**
     * Set visual property of single data item
     *
     * @param {number} idx
     * @param {string|Object} key
     * @param {*} [value]
     *
     * @example
     *  setItemVisual(0, 'color', color);
     *  setItemVisual(0, {
     *      'color': color
     *  });
     */
    // eslint-disable-next-line
    setItemVisual<K extends keyof Visual>(idx: number, key: K, value: Visual[K]): void;
    setItemVisual(idx: number, kvObject: Partial<Visual>): void;
    // eslint-disable-next-line
    setItemVisual<K extends keyof Visual>(idx: number, key: K | Partial<Visual>, value?: Visual[K]): void {
        const itemVisual = this._itemVisuals[idx] || {};
        this._itemVisuals[idx] = itemVisual;

        if (isObject(key)) {
            zrUtil.extend(itemVisual, key);
        }
        else {
            itemVisual[key as string] = value;
        }
    }

    /**
     * Clear itemVisuals and list visual.
     */
    clearAllVisual(): void {
        this._visual = {};
        this._itemVisuals = [];
1842
    }
S
sushuang 已提交
1843

1844 1845 1846 1847 1848 1849
    /**
     * Set layout property.
     */
    setLayout(key: string, val: any): void;
    setLayout(kvObj: Dictionary<any>): void;
    setLayout(key: string | Dictionary<any>, val?: any): void {
1850
        if (isObject(key)) {
1851
            for (const name in key) {
1852 1853 1854
                if (key.hasOwnProperty(name)) {
                    this.setLayout(name, key[name]);
                }
L
lang 已提交
1855
            }
1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866
            return;
        }
        this._layout[key] = val;
    }

    /**
     * Get layout property.
     */
    getLayout(key: string): any {
        return this._layout[key];
    }
S
sushuang 已提交
1867

1868 1869 1870
    /**
     * Get layout of single data item
     */
P
pissang 已提交
1871
    getItemLayout(idx: number): any {
1872 1873
        return this._itemLayouts[idx];
    }
S
sushuang 已提交
1874

1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886
    /**
     * Set layout of single data item
     */
    setItemLayout<M = false>(
        idx: number,
        layout: (M extends true ? Dictionary<any> : any),
        merge?: M
    ): void {
        this._itemLayouts[idx] = merge
            ? zrUtil.extend(this._itemLayouts[idx] || {}, layout)
            : layout;
    }
S
sushuang 已提交
1887

1888 1889 1890 1891 1892 1893
    /**
     * Clear all layout of single data item
     */
    clearItemLayouts(): void {
        this._itemLayouts.length = 0;
    }
S
sushuang 已提交
1894

1895 1896 1897 1898
    /**
     * Set graphic element relative to data. It can be set as null
     */
    setItemGraphicEl(idx: number, el: Element): void {
1899
        const hostModel = this.hostModel;
P
pissang 已提交
1900

1901
        if (el) {
1902
            const ecData = getECData(el);
1903 1904
            // Add data index and series index for indexing the data by element
            // Useful in tooltip
1905 1906 1907
            ecData.dataIndex = idx;
            ecData.dataType = this.dataType;
            ecData.seriesIndex = hostModel && (hostModel as any).seriesIndex;
1908 1909

            // TODO: not store dataIndex on children.
1910 1911 1912
            if (el.type === 'group') {
                el.traverse(setItemDataAndSeriesIndex, el);
            }
S
sushuang 已提交
1913 1914
        }

1915
        this._graphicEls[idx] = el;
S
sushuang 已提交
1916
    }
L
lang 已提交
1917

1918 1919 1920
    getItemGraphicEl(idx: number): Element {
        return this._graphicEls[idx];
    }
P
pissang 已提交
1921

1922 1923 1924 1925 1926 1927 1928 1929 1930 1931
    eachItemGraphicEl<Ctx = unknown>(
        cb: (this: Ctx, el: Element, idx: number) => void,
        context?: Ctx
    ): void {
        zrUtil.each(this._graphicEls, function (el, idx) {
            if (el) {
                cb && cb.call(context, el, idx);
            }
        });
    }
P
pissang 已提交
1932

1933 1934 1935 1936
    /**
     * Shallow clone a new list except visual and layout properties, and graph elements.
     * New list only change the indices.
     */
P
pissang 已提交
1937
    cloneShallow(list?: List<HostModel>): List<HostModel> {
1938
        if (!list) {
P
pissang 已提交
1939
            const dimensionInfoList = map(this.dimensions, this.getDimensionInfo, this);
1940 1941
            list = new List(dimensionInfoList, this.hostModel);
        }
L
lang 已提交
1942

1943 1944
        // FIXME
        list._storage = this._storage;
1945
        list._storageArr = this._storageArr;
S
sushuang 已提交
1946

1947
        transferProperties(list, this);
S
sushuang 已提交
1948

1949 1950
        // Clone will not change the data extent and indices
        if (this._indices) {
1951
            const Ctor = this._indices.constructor as DataArrayLikeConstructor;
1952
            if (Ctor === Array) {
1953
                const thisCount = this._indices.length;
1954
                list._indices = new Ctor(thisCount);
1955
                for (let i = 0; i < thisCount; i++) {
1956 1957 1958 1959 1960
                    list._indices[i] = this._indices[i];
                }
            }
            else {
                list._indices = new (Ctor as DataTypedArrayConstructor)(this._indices);
L
lang 已提交
1961
            }
L
lang 已提交
1962
        }
1963 1964 1965 1966 1967 1968
        else {
            list._indices = null;
        }
        list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;

        return list;
S
sushuang 已提交
1969
    }
L
lang 已提交
1970

1971 1972 1973 1974 1975 1976 1977
    /**
     * Wrap some method to add more feature
     */
    wrapMethod(
        methodName: FunctionPropertyNames<List>,
        injectFunction: (...args: any) => any
    ): void {
1978
        const originalMethod = this[methodName];
1979 1980
        if (typeof originalMethod !== 'function') {
            return;
1981
        }
1982 1983 1984
        this.__wrappedMethods = this.__wrappedMethods || [];
        this.__wrappedMethods.push(methodName);
        this[methodName] = function () {
1985
            const res = (originalMethod as any).apply(this, arguments);
1986 1987
            return injectFunction.apply(this, [res].concat(zrUtil.slice(arguments)));
        };
S
sushuang 已提交
1988
    }
D
deqingli 已提交
1989 1990


1991 1992 1993
    // ----------------------------------------------------------
    // A work around for internal method visiting private member.
    // ----------------------------------------------------------
P
pissang 已提交
1994
    private static internalField = (function () {
L
lang 已提交
1995

1996
        defaultDimValueGetters = {
1997

1998
            arrayRows: getDimValueSimply,
L
lang 已提交
1999

2000 2001
            objectRows: function (
                this: List, dataItem: Dictionary<any>, dimName: string, dataIndex: number, dimIndex: number
2002
            ): ParsedValue {
1
100pah 已提交
2003
                return parseDataValue(dataItem[dimName], this._dimensionInfos[dimName]);
2004
            },
L
lang 已提交
2005

2006 2007 2008 2009
            keyedColumns: getDimValueSimply,

            original: function (
                this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number
2010
            ): ParsedValue {
2011
                // Performance sensitive, do not use modelUtil.getDataItemValue.
2012
                // If dataItem is an plain object with no value field, the let `value`
2013 2014
                // will be assigned with the object, but it will be tread correctly
                // in the `convertDataValue`.
2015
                const value = dataItem && (dataItem.value == null ? dataItem : dataItem.value);
2016 2017 2018 2019 2020

                // If any dataItem is like { value: 10 }
                if (!this._rawData.pure && isDataItemOption(dataItem)) {
                    this.hasItemOption = true;
                }
1
100pah 已提交
2021
                return parseDataValue(
2022 2023 2024 2025 2026 2027 2028 2029 2030 2031
                    (value instanceof Array)
                        ? value[dimIndex]
                        // If value is a single number or something else not array.
                        : value,
                    this._dimensionInfos[dimName]
                );
            },

            typedArray: function (
                this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number
2032
            ): ParsedValue {
2033
                return dataItem[dimIndex];
L
lang 已提交
2034
            }
P
pah100 已提交
2035

2036
        };
S
sushuang 已提交
2037

2038 2039
        function getDimValueSimply(
            this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number
2040
        ): ParsedValue {
1
100pah 已提交
2041 2042
            return parseDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]);
        }
2043 2044

        prepareInvertedIndex = function (list: List): void {
2045
            const invertedIndicesMap = list._invertedIndicesMap;
2046
            zrUtil.each(invertedIndicesMap, function (invertedIndices, dim) {
2047
                const dimInfo = list._dimensionInfos[dim];
2048 2049

                // Currently, only dimensions that has ordinalMeta can create inverted indices.
2050
                const ordinalMeta = dimInfo.ordinalMeta;
2051 2052 2053 2054 2055 2056
                if (ordinalMeta) {
                    invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array(
                        ordinalMeta.categories.length
                    );
                    // The default value of TypedArray is 0. To avoid miss
                    // mapping to 0, we should set it as INDEX_NOT_FOUND.
1
100pah 已提交
2057
                    for (let i = 0; i < invertedIndices.length; i++) {
2058 2059
                        invertedIndices[i] = INDEX_NOT_FOUND;
                    }
1
100pah 已提交
2060
                    for (let i = 0; i < list._count; i++) {
2061 2062 2063 2064 2065 2066 2067
                        // Only support the case that all values are distinct.
                        invertedIndices[list.get(dim, i) as number] = i;
                    }
                }
            });
        };

2068 2069 2070
        getRawValueFromStore = function (
            list: List, dimIndex: number, rawIndex: number
        ): ParsedValue | OrdinalRawValue {
2071
            let val;
2072
            if (dimIndex != null) {
2073
                const dim = list.dimensions[dimIndex];
P
pissang 已提交
2074
                const chunk = list._storage[dim];
2075
                if (chunk) {
P
pissang 已提交
2076
                    val = chunk[rawIndex];
2077
                    const ordinalMeta = list._dimensionInfos[dim].ordinalMeta;
2078
                    if (ordinalMeta && ordinalMeta.categories.length) {
1
100pah 已提交
2079
                        val = ordinalMeta.categories[val as OrdinalNumber];
2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090
                    }
                }
            }
            return val;
        };

        getIndicesCtor = function (list: List): DataArrayLikeConstructor {
            // The possible max value in this._indicies is always this._rawCount despite of filtering.
            return list._rawCount > 65535 ? CtorUint32Array : CtorUint16Array;
        };

P
pissang 已提交
2091
        prepareStorage = function (
2092 2093
            storage: DataStorage,
            dimInfo: DataDimensionInfo,
P
pissang 已提交
2094 2095
            end: number,
            append?: boolean
2096
        ): void {
2097 2098
            const DataCtor = dataCtors[dimInfo.type];
            const dim = dimInfo.name;
P
pissang 已提交
2099 2100 2101

            if (append) {
                const oldStore = storage[dim];
P
pissang 已提交
2102 2103
                const oldLen = oldStore && oldStore.length;
                if (!(oldLen === end)) {
P
pissang 已提交
2104 2105 2106 2107 2108 2109 2110
                    const newStore = new DataCtor(end);
                    // The cost of the copy is probably inconsiderable
                    // within the initial chunkSize.
                    for (let j = 0; j < oldLen; j++) {
                        newStore[j] = oldStore[j];
                    }
                    storage[dim] = newStore;
2111 2112
                }
            }
P
pissang 已提交
2113 2114
            else {
                storage[dim] = new DataCtor(end);
2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128
            }
        };

        getRawIndexWithoutIndices = function (this: List, idx: number): number {
            return idx;
        };

        getRawIndexWithIndices = function (this: List, idx: number): number {
            if (idx < this._count && idx >= 0) {
                return this._indices[idx];
            }
            return -1;
        };

2129 2130 2131
        /**
         * @see the comment of `List['getId']`.
         */
2132
        getId = function (list: List, rawIndex: number): string {
2133
            let id = list._idList[rawIndex];
2134
            if (id == null) {
2135
                id = convertOptionIdName(getRawValueFromStore(list, list._idDimIdx, rawIndex), null);
2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153
            }
            if (id == null) {
                // FIXME Check the usage in graph, should not use prefix.
                id = ID_PREFIX + rawIndex;
            }
            return id;
        };

        normalizeDimensions = function (
            dimensions: ItrParamDims
        ): Array<DimensionLoose> {
            if (!zrUtil.isArray(dimensions)) {
                dimensions = [dimensions];
            }
            return dimensions;
        };

        validateDimensions = function (list: List, dims: DimensionName[]): void {
2154
            for (let i = 0; i < dims.length; i++) {
2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166
                // stroage may be empty when no data, so use
                // dimensionInfos to check.
                if (!list._dimensionInfos[dims[i]]) {
                    console.error('Unkown dimension ' + dims[i]);
                }
            }
        };

        // Data in excludeDimensions is copied, otherwise transfered.
        cloneListForMapAndSample = function (
            original: List, excludeDimensions: DimensionName[]
        ): List {
2167 2168
            const allDimensions = original.dimensions;
            const list = new List(
P
pissang 已提交
2169
                map(allDimensions, original.getDimensionInfo, original),
2170 2171 2172 2173 2174
                original.hostModel
            );
            // FIXME If needs stackedOn, value may already been stacked
            transferProperties(list, original);

2175 2176
            const storage = list._storage = {} as DataStorage;
            const originalStorage = original._storage;
P
pissang 已提交
2177
            const storageArr: DataValueChunk[] = list._storageArr = [];
2178 2179

            // Init storage
2180
            for (let i = 0; i < allDimensions.length; i++) {
2181
                const dim = allDimensions[i];
2182 2183 2184 2185
                if (originalStorage[dim]) {
                    // Notice that we do not reset invertedIndicesMap here, becuase
                    // there is no scenario of mapping or sampling ordinal dimension.
                    if (zrUtil.indexOf(excludeDimensions, dim) >= 0) {
P
pissang 已提交
2186
                        storage[dim] = cloneChunk(originalStorage[dim]);
2187 2188 2189 2190 2191 2192 2193
                        list._rawExtent[dim] = getInitialExtent();
                        list._extent[dim] = null;
                    }
                    else {
                        // Direct reference for other dimensions
                        storage[dim] = originalStorage[dim];
                    }
P
pissang 已提交
2194
                    storageArr.push(storage[dim]);
2195 2196 2197 2198 2199 2200
                }
            }
            return list;
        };

        function cloneChunk(originalChunk: DataValueChunk): DataValueChunk {
2201
            const Ctor = originalChunk.constructor;
2202 2203
            // Only shallow clone is enough when Array.
            return Ctor === Array
2204
                ? (originalChunk as Array<ParsedValue>).slice()
2205
                : new (Ctor as DataTypedArrayConstructor)(originalChunk as DataTypedArray);
S
sushuang 已提交
2206
        }
L
lang 已提交
2207

2208 2209 2210 2211 2212
        getInitialExtent = function (): [number, number] {
            return [Infinity, -Infinity];
        };

        setItemDataAndSeriesIndex = function (this: Element, child: Element): void {
2213 2214
            const childECData = getECData(child);
            const thisECData = getECData(this);
2215 2216 2217
            childECData.seriesIndex = thisECData.seriesIndex;
            childECData.dataIndex = thisECData.dataIndex;
            childECData.dataType = thisECData.dataType;
2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228
        };

        transferProperties = function (target: List, source: List): void {
            zrUtil.each(
                TRANSFERABLE_PROPERTIES.concat(source.__wrappedMethods || []),
                function (propName) {
                    if (source.hasOwnProperty(propName)) {
                        (target as any)[propName] = (source as any)[propName];
                    }
                }
            );
L
lang 已提交
2229

2230
            target.__wrappedMethods = source.__wrappedMethods;
L
lang 已提交
2231

2232 2233 2234
            zrUtil.each(CLONE_PROPERTIES, function (propName) {
                (target as any)[propName] = zrUtil.clone((source as any)[propName]);
            });
L
lang 已提交
2235

2236 2237
            target._calculationInfo = zrUtil.extend({}, source._calculationInfo);
        };
P
pah100 已提交
2238

2239
    })();
L
lang 已提交
2240

2241 2242
}

2243 2244 2245 2246 2247
interface List {
    getLinkedData(dataType?: SeriesDataType): List;
    getLinkedDataAll(): { data: List, type?: SeriesDataType }[];
}

S
sushuang 已提交
2248
export default List;