| /* | 
 | * Licensed to the Apache Software Foundation (ASF) under one | 
 | * or more contributor license agreements.  See the NOTICE file | 
 | * distributed with this work for additional information | 
 | * regarding copyright ownership.  The ASF licenses this file | 
 | * to you under the Apache License, Version 2.0 (the | 
 | * "License"); you may not use this file except in compliance | 
 | * with the License.  You may obtain a copy of the License at | 
 | * | 
 | *   http://www.apache.org/licenses/LICENSE-2.0 | 
 | * | 
 | * Unless required by applicable law or agreed to in writing, | 
 | * software distributed under the License is distributed on an | 
 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
 | * KIND, either express or implied.  See the License for the | 
 | * specific language governing permissions and limitations | 
 | * under the License. | 
 | */ | 
 |  | 
 | import { | 
 |     DimensionDefinitionLoose, OptionEncode, OptionEncodeValue, | 
 |     EncodeDefaulter, | 
 |     OptionSourceData, | 
 |     DimensionName, | 
 |     DimensionDefinition, | 
 |     DataVisualDimensions, | 
 |     DimensionIndex, | 
 |     VISUAL_DIMENSIONS | 
 | } from '../../util/types'; | 
 | import SeriesDimensionDefine from '../SeriesDimensionDefine'; | 
 | import { | 
 |     createHashMap, defaults, each, extend, HashMap, isObject, isString | 
 | } from 'zrender/src/core/util'; | 
 | import OrdinalMeta from '../OrdinalMeta'; | 
 | import { createSourceFromSeriesDataOption, isSourceInstance, Source } from '../Source'; | 
 | import { CtorInt32Array } from '../DataStore'; | 
 | import { normalizeToArray } from '../../util/model'; | 
 | import { BE_ORDINAL, guessOrdinal } from './sourceHelper'; | 
 | import { | 
 |     createDimNameMap, ensureSourceDimNameMap, SeriesDataSchema, shouldOmitUnusedDimensions | 
 | } from './SeriesDataSchema'; | 
 |  | 
 |  | 
 | export interface CoordDimensionDefinition extends DimensionDefinition { | 
 |     dimsDef?: (DimensionName | { name: DimensionName, defaultTooltip?: boolean })[]; | 
 |     otherDims?: DataVisualDimensions; | 
 |     ordinalMeta?: OrdinalMeta; | 
 |     coordDim?: DimensionName; | 
 |     coordDimIndex?: DimensionIndex; | 
 | } | 
 | export type CoordDimensionDefinitionLoose = CoordDimensionDefinition['name'] | CoordDimensionDefinition; | 
 |  | 
 | export type PrepareSeriesDataSchemaParams = { | 
 |     coordDimensions?: CoordDimensionDefinitionLoose[], | 
 |     /** | 
 |      * Will use `source.dimensionsDefine` if not given. | 
 |      */ | 
 |     dimensionsDefine?: DimensionDefinitionLoose[], | 
 |     /** | 
 |      * Will use `source.encodeDefine` if not given. | 
 |      */ | 
 |     encodeDefine?: HashMap<OptionEncodeValue, DimensionName> | OptionEncode, | 
 |     dimensionsCount?: number, | 
 |     /** | 
 |      * Make default encode if user not specified. | 
 |      */ | 
 |     encodeDefaulter?: EncodeDefaulter, | 
 |     generateCoord?: string, | 
 |     generateCoordCount?: number, | 
 |  | 
 |     /** | 
 |      * If be able to omit unused dimension | 
 |      * Used to improve the performance on high dimension data. | 
 |      */ | 
 |     canOmitUnusedDimensions?: boolean | 
 | }; | 
 |  | 
 | /** | 
 |  * For outside usage compat (like echarts-gl are using it). | 
 |  */ | 
 | export function createDimensions( | 
 |     source: Source | OptionSourceData, | 
 |     opt?: PrepareSeriesDataSchemaParams | 
 | ): SeriesDimensionDefine[] { | 
 |     return prepareSeriesDataSchema(source, opt).dimensions; | 
 | } | 
 |  | 
 | /** | 
 |  * This method builds the relationship between: | 
 |  * + "what the coord sys or series requires (see `coordDimensions`)", | 
 |  * + "what the user defines (in `encode` and `dimensions`, see `opt.dimensionsDefine` and `opt.encodeDefine`)" | 
 |  * + "what the data source provids (see `source`)". | 
 |  * | 
 |  * Some guess strategy will be adapted if user does not define something. | 
 |  * If no 'value' dimension specified, the first no-named dimension will be | 
 |  * named as 'value'. | 
 |  * | 
 |  * @return The results are always sorted by `storeDimIndex` asc. | 
 |  */ | 
 | export default function prepareSeriesDataSchema( | 
 |     // TODO: TYPE completeDimensions type | 
 |     source: Source | OptionSourceData, | 
 |     opt?: PrepareSeriesDataSchemaParams | 
 | ): SeriesDataSchema { | 
 |     if (!isSourceInstance(source)) { | 
 |         source = createSourceFromSeriesDataOption(source as OptionSourceData); | 
 |     } | 
 |  | 
 |     opt = opt || {}; | 
 |  | 
 |     const sysDims = opt.coordDimensions || []; | 
 |     const dimsDef = opt.dimensionsDefine || source.dimensionsDefine || []; | 
 |     const coordDimNameMap = createHashMap<true, DimensionName>(); | 
 |     const resultList: SeriesDimensionDefine[] = []; | 
 |     const dimCount = getDimCount(source, sysDims, dimsDef, opt.dimensionsCount); | 
 |  | 
 |     // Try to ignore unused dimensions if sharing a high dimension datastore | 
 |     // 30 is an experience value. | 
 |     const omitUnusedDimensions = opt.canOmitUnusedDimensions && shouldOmitUnusedDimensions(dimCount); | 
 |  | 
 |     const isUsingSourceDimensionsDef = dimsDef === source.dimensionsDefine; | 
 |     const dataDimNameMap = isUsingSourceDimensionsDef | 
 |         ? ensureSourceDimNameMap(source) : createDimNameMap(dimsDef); | 
 |  | 
 |     let encodeDef = opt.encodeDefine; | 
 |     if (!encodeDef && opt.encodeDefaulter) { | 
 |         encodeDef = opt.encodeDefaulter(source, dimCount); | 
 |     } | 
 |     const encodeDefMap = createHashMap<DimensionIndex[] | false, DimensionName>(encodeDef as any); | 
 |  | 
 |     const indicesMap = new CtorInt32Array(dimCount); | 
 |     for (let i = 0; i < indicesMap.length; i++) { | 
 |         indicesMap[i] = -1; | 
 |     } | 
 |  | 
 |     function getResultItem(dimIdx: number) { | 
 |         const idx = indicesMap[dimIdx]; | 
 |         if (idx < 0) { | 
 |             const dimDefItemRaw = dimsDef[dimIdx]; | 
 |             const dimDefItem = isObject(dimDefItemRaw) ? dimDefItemRaw : { name: dimDefItemRaw }; | 
 |             const resultItem = new SeriesDimensionDefine(); | 
 |             const userDimName = dimDefItem.name; | 
 |             if (userDimName != null && dataDimNameMap.get(userDimName) != null) { | 
 |                 // Only if `series.dimensions` is defined in option | 
 |                 // displayName, will be set, and dimension will be displayed vertically in | 
 |                 // tooltip by default. | 
 |                 resultItem.name = resultItem.displayName = userDimName; | 
 |             } | 
 |             dimDefItem.type != null && (resultItem.type = dimDefItem.type); | 
 |             dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName); | 
 |             const newIdx = resultList.length; | 
 |             indicesMap[dimIdx] = newIdx; | 
 |             resultItem.storeDimIndex = dimIdx; | 
 |             resultList.push(resultItem); | 
 |             return resultItem; | 
 |         } | 
 |         return resultList[idx]; | 
 |     } | 
 |  | 
 |     if (!omitUnusedDimensions) { | 
 |         for (let i = 0; i < dimCount; i++) { | 
 |             getResultItem(i); | 
 |         } | 
 |     } | 
 |  | 
 |     // Set `coordDim` and `coordDimIndex` by `encodeDefMap` and normalize `encodeDefMap`. | 
 |     encodeDefMap.each(function (dataDimsRaw, coordDim) { | 
 |         const dataDims = normalizeToArray(dataDimsRaw as []).slice(); | 
 |  | 
 |         // Note: It is allowed that `dataDims.length` is `0`, e.g., options is | 
 |         // `{encode: {x: -1, y: 1}}`. Should not filter anything in | 
 |         // this case. | 
 |         if (dataDims.length === 1 && !isString(dataDims[0]) && dataDims[0] < 0) { | 
 |             encodeDefMap.set(coordDim, false); | 
 |             return; | 
 |         } | 
 |  | 
 |         const validDataDims = encodeDefMap.set(coordDim, []) as DimensionIndex[]; | 
 |         each(dataDims, function (resultDimIdxOrName, idx) { | 
 |             // The input resultDimIdx can be dim name or index. | 
 |             const resultDimIdx = isString(resultDimIdxOrName) | 
 |                 ? dataDimNameMap.get(resultDimIdxOrName) | 
 |                 : resultDimIdxOrName; | 
 |             if (resultDimIdx != null && resultDimIdx < dimCount) { | 
 |                 validDataDims[idx] = resultDimIdx; | 
 |                 applyDim(getResultItem(resultDimIdx), coordDim, idx); | 
 |             } | 
 |         }); | 
 |     }); | 
 |  | 
 |     // Apply templates and default order from `sysDims`. | 
 |     let availDimIdx = 0; | 
 |     each(sysDims, function (sysDimItemRaw) { | 
 |         let coordDim: DimensionName; | 
 |         let sysDimItemDimsDef: CoordDimensionDefinition['dimsDef']; | 
 |         let sysDimItemOtherDims: CoordDimensionDefinition['otherDims']; | 
 |         let sysDimItem: CoordDimensionDefinition; | 
 |         if (isString(sysDimItemRaw)) { | 
 |             coordDim = sysDimItemRaw; | 
 |             sysDimItem = {} as CoordDimensionDefinition; | 
 |         } | 
 |         else { | 
 |             sysDimItem = sysDimItemRaw; | 
 |             coordDim = sysDimItem.name; | 
 |             const ordinalMeta = sysDimItem.ordinalMeta; | 
 |             sysDimItem.ordinalMeta = null; | 
 |             sysDimItem = extend({}, sysDimItem); | 
 |             sysDimItem.ordinalMeta = ordinalMeta; | 
 |             // `coordDimIndex` should not be set directly. | 
 |             sysDimItemDimsDef = sysDimItem.dimsDef; | 
 |             sysDimItemOtherDims = sysDimItem.otherDims; | 
 |             sysDimItem.name = sysDimItem.coordDim = sysDimItem.coordDimIndex = | 
 |                 sysDimItem.dimsDef = sysDimItem.otherDims = null; | 
 |         } | 
 |  | 
 |         let dataDims = encodeDefMap.get(coordDim); | 
 |  | 
 |         // negative resultDimIdx means no need to mapping. | 
 |         if (dataDims === false) { | 
 |             return; | 
 |         } | 
 |  | 
 |         dataDims = normalizeToArray(dataDims); | 
 |  | 
 |         // dimensions provides default dim sequences. | 
 |         if (!dataDims.length) { | 
 |             for (let i = 0; i < (sysDimItemDimsDef && sysDimItemDimsDef.length || 1); i++) { | 
 |                 while (availDimIdx < dimCount && getResultItem(availDimIdx).coordDim != null) { | 
 |                     availDimIdx++; | 
 |                 } | 
 |                 availDimIdx < dimCount && dataDims.push(availDimIdx++); | 
 |             } | 
 |         } | 
 |  | 
 |         // Apply templates. | 
 |         each(dataDims, function (resultDimIdx, coordDimIndex) { | 
 |             const resultItem = getResultItem(resultDimIdx); | 
 |             // Coordinate system has a higher priority on dim type than source. | 
 |             if (isUsingSourceDimensionsDef && sysDimItem.type != null) { | 
 |                 resultItem.type = sysDimItem.type; | 
 |             } | 
 |             applyDim(defaults(resultItem, sysDimItem), coordDim, coordDimIndex); | 
 |             if (resultItem.name == null && sysDimItemDimsDef) { | 
 |                 let sysDimItemDimsDefItem = sysDimItemDimsDef[coordDimIndex]; | 
 |                 !isObject(sysDimItemDimsDefItem) && (sysDimItemDimsDefItem = { | 
 |                     name: sysDimItemDimsDefItem | 
 |                 }); | 
 |                 resultItem.name = resultItem.displayName = sysDimItemDimsDefItem.name; | 
 |                 resultItem.defaultTooltip = sysDimItemDimsDefItem.defaultTooltip; | 
 |             } | 
 |             // FIXME refactor, currently only used in case: {otherDims: {tooltip: false}} | 
 |             sysDimItemOtherDims && defaults(resultItem.otherDims, sysDimItemOtherDims); | 
 |         }); | 
 |     }); | 
 |  | 
 |     function applyDim(resultItem: SeriesDimensionDefine, coordDim: DimensionName, coordDimIndex: DimensionIndex) { | 
 |         if (VISUAL_DIMENSIONS.get(coordDim as keyof DataVisualDimensions) != null) { | 
 |             resultItem.otherDims[coordDim as keyof DataVisualDimensions] = coordDimIndex; | 
 |         } | 
 |         else { | 
 |             resultItem.coordDim = coordDim; | 
 |             resultItem.coordDimIndex = coordDimIndex; | 
 |             coordDimNameMap.set(coordDim, true); | 
 |         } | 
 |     } | 
 |  | 
 |     // Make sure the first extra dim is 'value'. | 
 |     const generateCoord = opt.generateCoord; | 
 |     let generateCoordCount = opt.generateCoordCount; | 
 |     const fromZero = generateCoordCount != null; | 
 |     generateCoordCount = generateCoord ? (generateCoordCount || 1) : 0; | 
 |     const extra = generateCoord || 'value'; | 
 |  | 
 |     function ifNoNameFillWithCoordName(resultItem: SeriesDimensionDefine): void { | 
 |         if (resultItem.name == null) { | 
 |             // Duplication will be removed in the next step. | 
 |             resultItem.name = resultItem.coordDim; | 
 |         } | 
 |     } | 
 |  | 
 |     // Set dim `name` and other `coordDim` and other props. | 
 |     if (!omitUnusedDimensions) { | 
 |         for (let resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) { | 
 |             const resultItem = getResultItem(resultDimIdx); | 
 |             const coordDim = resultItem.coordDim; | 
 |  | 
 |             if (coordDim == null) { | 
 |                 // TODO no need to generate coordDim for isExtraCoord? | 
 |                 resultItem.coordDim = genCoordDimName( | 
 |                     extra, coordDimNameMap, fromZero | 
 |                 ); | 
 |  | 
 |                 resultItem.coordDimIndex = 0; | 
 |                 // Series specified generateCoord is using out. | 
 |                 if (!generateCoord || generateCoordCount <= 0) { | 
 |                     resultItem.isExtraCoord = true; | 
 |                 } | 
 |                 generateCoordCount--; | 
 |             } | 
 |  | 
 |             ifNoNameFillWithCoordName(resultItem); | 
 |  | 
 |             if (resultItem.type == null | 
 |                 && ( | 
 |                     guessOrdinal(source, resultDimIdx) === BE_ORDINAL.Must | 
 |                     // Consider the case: | 
 |                     // { | 
 |                     //    dataset: {source: [ | 
 |                     //        ['2001', 123], | 
 |                     //        ['2002', 456], | 
 |                     //        ... | 
 |                     //        ['The others', 987], | 
 |                     //    ]}, | 
 |                     //    series: {type: 'pie'} | 
 |                     // } | 
 |                     // The first column should better be treated as a "ordinal" although it | 
 |                     // might not be detected as an "ordinal" by `guessOrdinal`. | 
 |                     || (resultItem.isExtraCoord | 
 |                         && (resultItem.otherDims.itemName != null | 
 |                             || resultItem.otherDims.seriesName != null | 
 |                         ) | 
 |                     ) | 
 |                 ) | 
 |             ) { | 
 |                 resultItem.type = 'ordinal'; | 
 |             } | 
 |         } | 
 |     } | 
 |     else { | 
 |         each(resultList, resultItem => { | 
 |             // PENDING: guessOrdinal or let user specify type: 'ordinal' manually? | 
 |             ifNoNameFillWithCoordName(resultItem); | 
 |         }); | 
 |         // Sort dimensions: there are some rule that use the last dim as label, | 
 |         // and for some latter travel process easier. | 
 |         resultList.sort((item0, item1) => item0.storeDimIndex - item1.storeDimIndex); | 
 |     } | 
 |  | 
 |     removeDuplication(resultList); | 
 |  | 
 |     return new SeriesDataSchema({ | 
 |         source, | 
 |         dimensions: resultList, | 
 |         fullDimensionCount: dimCount, | 
 |         dimensionOmitted: omitUnusedDimensions | 
 |     }); | 
 | } | 
 |  | 
 | function removeDuplication(result: SeriesDimensionDefine[]) { | 
 |     const duplicationMap = createHashMap<number>(); | 
 |     for (let i = 0; i < result.length; i++) { | 
 |         const dim = result[i]; | 
 |         const dimOriginalName = dim.name; | 
 |         let count = duplicationMap.get(dimOriginalName) || 0; | 
 |         if (count > 0) { | 
 |             // Starts from 0. | 
 |             dim.name = dimOriginalName + (count - 1); | 
 |         } | 
 |         count++; | 
 |         duplicationMap.set(dimOriginalName, count); | 
 |     } | 
 | } | 
 |  | 
 | // ??? TODO | 
 | // Originally detect dimCount by data[0]. Should we | 
 | // optimize it to only by sysDims and dimensions and encode. | 
 | // So only necessary dims will be initialized. | 
 | // But | 
 | // (1) custom series should be considered. where other dims | 
 | // may be visited. | 
 | // (2) sometimes user need to calculate bubble size or use visualMap | 
 | // on other dimensions besides coordSys needed. | 
 | // So, dims that is not used by system, should be shared in data store? | 
 | function getDimCount( | 
 |     source: Source, | 
 |     sysDims: CoordDimensionDefinitionLoose[], | 
 |     dimsDef: DimensionDefinitionLoose[], | 
 |     optDimCount?: number | 
 | ): number { | 
 |     // Note that the result dimCount should not small than columns count | 
 |     // of data, otherwise `dataDimNameMap` checking will be incorrect. | 
 |     let dimCount = Math.max( | 
 |         source.dimensionsDetectedCount || 1, | 
 |         sysDims.length, | 
 |         dimsDef.length, | 
 |         optDimCount || 0 | 
 |     ); | 
 |     each(sysDims, function (sysDimItem) { | 
 |         let sysDimItemDimsDef; | 
 |         if (isObject(sysDimItem) && (sysDimItemDimsDef = sysDimItem.dimsDef)) { | 
 |             dimCount = Math.max(dimCount, sysDimItemDimsDef.length); | 
 |         } | 
 |     }); | 
 |     return dimCount; | 
 | } | 
 |  | 
 | function genCoordDimName( | 
 |     name: DimensionName, | 
 |     map: HashMap<unknown, DimensionName>, | 
 |     fromZero: boolean | 
 | ) { | 
 |     if (fromZero || map.hasKey(name)) { | 
 |         let i = 0; | 
 |         while (map.hasKey(name + i)) { | 
 |             i++; | 
 |         } | 
 |         name += i; | 
 |     } | 
 |     map.set(name, true); | 
 |     return name; | 
 | } |