| /* |
| * 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. |
| */ |
| |
| // TODO |
| // ??? refactor? check the outer usage of data provider. |
| // merge with defaultDimValueGetter? |
| |
| import {isTypedArray, extend, assert, each, isObject, bind} from 'zrender/src/core/util'; |
| import {getDataItemValue} from '../../util/model'; |
| import { createSourceFromSeriesDataOption, Source, isSourceInstance } from '../Source'; |
| import {ArrayLike, Dictionary} from 'zrender/src/core/types'; |
| import { |
| SOURCE_FORMAT_ORIGINAL, |
| SOURCE_FORMAT_OBJECT_ROWS, |
| SOURCE_FORMAT_KEYED_COLUMNS, |
| SOURCE_FORMAT_TYPED_ARRAY, |
| SOURCE_FORMAT_ARRAY_ROWS, |
| SERIES_LAYOUT_BY_COLUMN, |
| SERIES_LAYOUT_BY_ROW, |
| DimensionName, DimensionIndex, OptionSourceData, |
| DimensionIndexLoose, OptionDataItem, OptionDataValue, SourceFormat, SeriesLayoutBy, ParsedValue |
| } from '../../util/types'; |
| import List from '../List'; |
| |
| export interface DataProvider { |
| /** |
| * true: all of the value are in primitive type (in type `OptionDataValue`). |
| * false: Not sure whether any of them is non primitive type (in type `OptionDataItemObject`). |
| * Like `data: [ { value: xx, itemStyle: {...} }, ...]` |
| * At present it only happen in `SOURCE_FORMAT_ORIGINAL`. |
| */ |
| pure?: boolean; |
| /** |
| * If data is persistent and will not be released after use. |
| */ |
| persistent?: boolean; |
| |
| getSource(): Source; |
| count(): number; |
| getItem(idx: number, out?: OptionDataItem): OptionDataItem; |
| fillStorage?( |
| start: number, |
| end: number, |
| out: ArrayLike<ParsedValue>[], |
| extent: number[][] |
| ): void |
| appendData?(newData: ArrayLike<OptionDataItem>): void; |
| clean?(): void; |
| } |
| |
| |
| let providerMethods: Dictionary<any>; |
| let mountMethods: (provider: DefaultDataProvider, data: OptionSourceData, source: Source) => void; |
| |
| export interface DefaultDataProvider { |
| fillStorage?( |
| start: number, |
| end: number, |
| out: ArrayLike<ParsedValue>[], |
| extent: number[][] |
| ): void |
| } |
| /** |
| * If normal array used, mutable chunk size is supported. |
| * If typed array used, chunk size must be fixed. |
| */ |
| export class DefaultDataProvider implements DataProvider { |
| |
| private _source: Source; |
| |
| private _data: OptionSourceData; |
| |
| private _offset: number; |
| |
| private _dimSize: number; |
| |
| pure: boolean; |
| |
| persistent: boolean; |
| |
| static protoInitialize = (function () { |
| // PENDING: To avoid potential incompat (e.g., prototype |
| // is visited somewhere), still init them on prototype. |
| const proto = DefaultDataProvider.prototype; |
| proto.pure = false; |
| proto.persistent = true; |
| })(); |
| |
| |
| constructor(sourceParam: Source | OptionSourceData, dimSize?: number) { |
| // let source: Source; |
| const source: Source = !isSourceInstance(sourceParam) |
| ? createSourceFromSeriesDataOption(sourceParam as OptionSourceData) |
| : sourceParam as Source; |
| |
| // declare source is Source; |
| this._source = source; |
| const data = this._data = source.data; |
| |
| // Typed array. TODO IE10+? |
| if (source.sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) { |
| if (__DEV__) { |
| if (dimSize == null) { |
| throw new Error('Typed array data must specify dimension size'); |
| } |
| } |
| this._offset = 0; |
| this._dimSize = dimSize; |
| this._data = data; |
| } |
| |
| mountMethods(this, data, source); |
| } |
| |
| getSource(): Source { |
| return this._source; |
| } |
| |
| count(): number { |
| return 0; |
| } |
| |
| getItem(idx: number, out?: ArrayLike<number>): OptionDataItem { |
| return; |
| } |
| |
| appendData(newData: OptionSourceData): void { |
| } |
| |
| clean(): void { |
| } |
| |
| private static internalField = (function () { |
| |
| mountMethods = function (provider, data, source) { |
| const sourceFormat = source.sourceFormat; |
| const seriesLayoutBy = source.seriesLayoutBy; |
| const startIndex = source.startIndex; |
| const dimsDef = source.dimensionsDefine; |
| |
| const methods = providerMethods[getMethodMapKey(sourceFormat, seriesLayoutBy)]; |
| if (__DEV__) { |
| assert(methods, 'Invalide sourceFormat: ' + sourceFormat); |
| } |
| |
| extend(provider, methods); |
| |
| if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) { |
| provider.getItem = getItemForTypedArray; |
| provider.count = countForTypedArray; |
| provider.fillStorage = fillStorageForTypedArray; |
| } |
| else { |
| const rawItemGetter = getRawSourceItemGetter(sourceFormat, seriesLayoutBy); |
| provider.getItem = bind(rawItemGetter, null, data, startIndex, dimsDef); |
| const rawCounter = getRawSourceDataCounter(sourceFormat, seriesLayoutBy); |
| provider.count = bind(rawCounter, null, data, startIndex, dimsDef); |
| } |
| }; |
| |
| const getItemForTypedArray: DefaultDataProvider['getItem'] = function ( |
| this: DefaultDataProvider, idx: number, out: ArrayLike<number> |
| ): ArrayLike<number> { |
| idx = idx - this._offset; |
| out = out || []; |
| const data = this._data; |
| const dimSize = this._dimSize; |
| const offset = dimSize * idx; |
| for (let i = 0; i < dimSize; i++) { |
| out[i] = (data as ArrayLike<number>)[offset + i]; |
| } |
| return out; |
| }; |
| |
| const fillStorageForTypedArray: DefaultDataProvider['fillStorage'] = function ( |
| this: DefaultDataProvider, start: number, end: number, storage: ArrayLike<ParsedValue>[], extent: number[][] |
| ) { |
| const data = this._data as ArrayLike<number>; |
| const dimSize = this._dimSize; |
| |
| for (let dim = 0; dim < dimSize; dim++) { |
| const dimExtent = extent[dim]; |
| let min = dimExtent[0] == null ? Infinity : dimExtent[0]; |
| let max = dimExtent[1] == null ? -Infinity : dimExtent[1]; |
| const count = end - start; |
| const arr = storage[dim]; |
| for (let i = 0; i < count; i++) { |
| const val = data[(start + i) * dimSize + dim]; |
| arr[start + i] = val; |
| val < min && (min = val); |
| val > max && (max = val); |
| } |
| dimExtent[0] = min; |
| dimExtent[1] = max; |
| } |
| }; |
| |
| const countForTypedArray: DefaultDataProvider['count'] = function ( |
| this: DefaultDataProvider |
| ) { |
| return this._data ? ((this._data as ArrayLike<number>).length / this._dimSize) : 0; |
| }; |
| |
| providerMethods = { |
| |
| [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_COLUMN]: { |
| pure: true, |
| appendData: appendDataSimply |
| }, |
| |
| [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW]: { |
| pure: true, |
| appendData: function () { |
| throw new Error('Do not support appendData when set seriesLayoutBy: "row".'); |
| } |
| }, |
| |
| [SOURCE_FORMAT_OBJECT_ROWS]: { |
| pure: true, |
| appendData: appendDataSimply |
| }, |
| |
| [SOURCE_FORMAT_KEYED_COLUMNS]: { |
| pure: true, |
| appendData: function (this: DefaultDataProvider, newData: Dictionary<OptionDataValue[]>) { |
| const data = this._data as Dictionary<OptionDataValue[]>; |
| each(newData, function (newCol, key) { |
| const oldCol = data[key] || (data[key] = []); |
| for (let i = 0; i < (newCol || []).length; i++) { |
| oldCol.push(newCol[i]); |
| } |
| }); |
| } |
| }, |
| |
| [SOURCE_FORMAT_ORIGINAL]: { |
| appendData: appendDataSimply |
| }, |
| |
| [SOURCE_FORMAT_TYPED_ARRAY]: { |
| persistent: false, |
| pure: true, |
| appendData: function (this: DefaultDataProvider, newData: ArrayLike<number>): void { |
| if (__DEV__) { |
| assert( |
| isTypedArray(newData), |
| 'Added data must be TypedArray if data in initialization is TypedArray' |
| ); |
| } |
| this._data = newData; |
| }, |
| |
| // Clean self if data is already used. |
| clean: function (this: DefaultDataProvider): void { |
| // PENDING |
| this._offset += this.count(); |
| this._data = null; |
| } |
| } |
| }; |
| |
| function appendDataSimply(this: DefaultDataProvider, newData: ArrayLike<OptionDataItem>): void { |
| for (let i = 0; i < newData.length; i++) { |
| (this._data as any[]).push(newData[i]); |
| } |
| } |
| |
| })(); |
| } |
| |
| |
| |
| type RawSourceItemGetter = ( |
| rawData: OptionSourceData, |
| startIndex: number, |
| dimsDef: { name?: DimensionName }[], |
| idx: number |
| ) => OptionDataItem; |
| |
| const getItemSimply: RawSourceItemGetter = function ( |
| rawData, startIndex, dimsDef, idx |
| ): OptionDataItem { |
| return (rawData as [])[idx]; |
| }; |
| |
| const rawSourceItemGetterMap: Dictionary<RawSourceItemGetter> = { |
| [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_COLUMN]: function ( |
| rawData, startIndex, dimsDef, idx |
| ): OptionDataValue[] { |
| return (rawData as OptionDataValue[][])[idx + startIndex]; |
| }, |
| [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW]: function ( |
| rawData, startIndex, dimsDef, idx |
| ): OptionDataValue[] { |
| idx += startIndex; |
| const item = []; |
| const data = rawData as OptionDataValue[][]; |
| for (let i = 0; i < data.length; i++) { |
| const row = data[i]; |
| item.push(row ? row[idx] : null); |
| } |
| return item; |
| }, |
| [SOURCE_FORMAT_OBJECT_ROWS]: getItemSimply, |
| [SOURCE_FORMAT_KEYED_COLUMNS]: function ( |
| rawData, startIndex, dimsDef, idx |
| ): OptionDataValue[] { |
| const item = []; |
| for (let i = 0; i < dimsDef.length; i++) { |
| const dimName = dimsDef[i].name; |
| if (__DEV__) { |
| if (dimName == null) { |
| throw new Error(); |
| } |
| } |
| const col = (rawData as Dictionary<OptionDataValue[]>)[dimName]; |
| item.push(col ? col[idx] : null); |
| } |
| return item; |
| }, |
| [SOURCE_FORMAT_ORIGINAL]: getItemSimply |
| }; |
| |
| export function getRawSourceItemGetter( |
| sourceFormat: SourceFormat, seriesLayoutBy: SeriesLayoutBy |
| ): RawSourceItemGetter { |
| const method = rawSourceItemGetterMap[getMethodMapKey(sourceFormat, seriesLayoutBy)]; |
| if (__DEV__) { |
| assert(method, 'Do not suppport get item on "' + sourceFormat + '", "' + seriesLayoutBy + '".'); |
| } |
| return method; |
| } |
| |
| |
| |
| |
| type RawSourceDataCounter = ( |
| rawData: OptionSourceData, |
| startIndex: number, |
| dimsDef: { name?: DimensionName }[] |
| ) => number; |
| |
| const countSimply: RawSourceDataCounter = function ( |
| rawData, startIndex, dimsDef |
| ) { |
| return (rawData as []).length; |
| }; |
| |
| const rawSourceDataCounterMap: Dictionary<RawSourceDataCounter> = { |
| [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_COLUMN]: function ( |
| rawData, startIndex, dimsDef |
| ) { |
| return Math.max(0, (rawData as OptionDataItem[][]).length - startIndex); |
| }, |
| [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW]: function ( |
| rawData, startIndex, dimsDef |
| ) { |
| const row = (rawData as OptionDataValue[][])[0]; |
| return row ? Math.max(0, row.length - startIndex) : 0; |
| }, |
| [SOURCE_FORMAT_OBJECT_ROWS]: countSimply, |
| [SOURCE_FORMAT_KEYED_COLUMNS]: function ( |
| rawData, startIndex, dimsDef |
| ) { |
| const dimName = dimsDef[0].name; |
| if (__DEV__) { |
| if (dimName == null) { |
| throw new Error(); |
| } |
| } |
| const col = (rawData as Dictionary<OptionDataValue[]>)[dimName]; |
| return col ? col.length : 0; |
| }, |
| [SOURCE_FORMAT_ORIGINAL]: countSimply |
| }; |
| |
| export function getRawSourceDataCounter( |
| sourceFormat: SourceFormat, seriesLayoutBy: SeriesLayoutBy |
| ): RawSourceDataCounter { |
| const method = rawSourceDataCounterMap[getMethodMapKey(sourceFormat, seriesLayoutBy)]; |
| if (__DEV__) { |
| assert(method, 'Do not suppport count on "' + sourceFormat + '", "' + seriesLayoutBy + '".'); |
| } |
| return method; |
| } |
| |
| |
| |
| // TODO |
| // merge it to dataProvider? |
| type RawSourceValueGetter = ( |
| dataItem: OptionDataItem, |
| dimIndex: DimensionIndex, |
| dimName: DimensionName |
| // If dimIndex is null/undefined, return OptionDataItem. |
| // Otherwise, return OptionDataValue. |
| ) => OptionDataValue | OptionDataItem; |
| |
| const getRawValueSimply = function ( |
| dataItem: ArrayLike<OptionDataValue>, dimIndex: number, dimName: string |
| ): OptionDataValue | ArrayLike<OptionDataValue> { |
| return dimIndex != null ? dataItem[dimIndex] : dataItem; |
| }; |
| |
| const rawSourceValueGetterMap: {[sourceFormat: string]: RawSourceValueGetter} = { |
| |
| [SOURCE_FORMAT_ARRAY_ROWS]: getRawValueSimply, |
| |
| [SOURCE_FORMAT_OBJECT_ROWS]: function ( |
| dataItem: Dictionary<OptionDataValue>, dimIndex: number, dimName: string |
| ): OptionDataValue | Dictionary<OptionDataValue> { |
| return dimIndex != null ? dataItem[dimName] : dataItem; |
| }, |
| |
| [SOURCE_FORMAT_KEYED_COLUMNS]: getRawValueSimply, |
| |
| [SOURCE_FORMAT_ORIGINAL]: function ( |
| dataItem: OptionDataItem, dimIndex: number, dimName: string |
| ): OptionDataValue | OptionDataItem { |
| // FIXME: In some case (markpoint in geo (geo-map.html)), |
| // dataItem is {coord: [...]} |
| const value = getDataItemValue(dataItem); |
| return (dimIndex == null || !(value instanceof Array)) |
| ? value |
| : value[dimIndex]; |
| }, |
| |
| [SOURCE_FORMAT_TYPED_ARRAY]: getRawValueSimply |
| }; |
| |
| export function getRawSourceValueGetter(sourceFormat: SourceFormat): RawSourceValueGetter { |
| const method = rawSourceValueGetterMap[sourceFormat]; |
| if (__DEV__) { |
| assert(method, 'Do not suppport get value on "' + sourceFormat + '".'); |
| } |
| return method; |
| } |
| |
| |
| function getMethodMapKey(sourceFormat: SourceFormat, seriesLayoutBy: SeriesLayoutBy): string { |
| return sourceFormat === SOURCE_FORMAT_ARRAY_ROWS |
| ? sourceFormat + '_' + seriesLayoutBy |
| : sourceFormat; |
| } |
| |
| |
| // ??? FIXME can these logic be more neat: getRawValue, getRawDataItem, |
| // Consider persistent. |
| // Caution: why use raw value to display on label or tooltip? |
| // A reason is to avoid format. For example time value we do not know |
| // how to format is expected. More over, if stack is used, calculated |
| // value may be 0.91000000001, which have brings trouble to display. |
| // TODO: consider how to treat null/undefined/NaN when display? |
| export function retrieveRawValue( |
| data: List, dataIndex: number, dim?: DimensionName | DimensionIndexLoose |
| // If dimIndex is null/undefined, return OptionDataItem. |
| // Otherwise, return OptionDataValue. |
| ): OptionDataValue | OptionDataItem { |
| if (!data) { |
| return; |
| } |
| |
| // Consider data may be not persistent. |
| const dataItem = data.getRawDataItem(dataIndex); |
| |
| if (dataItem == null) { |
| return; |
| } |
| |
| const sourceFormat = data.getProvider().getSource().sourceFormat; |
| let dimName; |
| let dimIndex; |
| |
| const dimInfo = data.getDimensionInfo(dim); |
| if (dimInfo) { |
| dimName = dimInfo.name; |
| dimIndex = dimInfo.index; |
| } |
| |
| return getRawSourceValueGetter(sourceFormat)(dataItem, dimIndex, dimName); |
| } |
| |
| |
| /** |
| * Compatible with some cases (in pie, map) like: |
| * data: [{name: 'xx', value: 5, selected: true}, ...] |
| * where only sourceFormat is 'original' and 'objectRows' supported. |
| * |
| * // TODO |
| * Supported detail options in data item when using 'arrayRows'. |
| * |
| * @param data |
| * @param dataIndex |
| * @param attr like 'selected' |
| */ |
| export function retrieveRawAttr(data: List, dataIndex: number, attr: string): any { |
| if (!data) { |
| return; |
| } |
| |
| const sourceFormat = data.getProvider().getSource().sourceFormat; |
| |
| if (sourceFormat !== SOURCE_FORMAT_ORIGINAL |
| && sourceFormat !== SOURCE_FORMAT_OBJECT_ROWS |
| ) { |
| return; |
| } |
| |
| let dataItem = data.getRawDataItem(dataIndex); |
| if (sourceFormat === SOURCE_FORMAT_ORIGINAL && !isObject(dataItem)) { |
| dataItem = null; |
| } |
| if (dataItem) { |
| return (dataItem as Dictionary<OptionDataValue>)[attr]; |
| } |
| } |