Spaces:
Running
Running
| /* | |
| * 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. | |
| */ | |
| /** | |
| * Linear continuous scale | |
| * http://en.wikipedia.org/wiki/Level_of_measurement | |
| */ | |
| // FIXME only one data | |
| import Scale from './Scale'; | |
| import OrdinalMeta from '../data/OrdinalMeta'; | |
| import SeriesData from '../data/SeriesData'; | |
| import * as scaleHelper from './helper'; | |
| import { | |
| OrdinalRawValue, | |
| OrdinalNumber, | |
| DimensionLoose, | |
| OrdinalSortInfo, | |
| OrdinalScaleTick, | |
| ScaleTick | |
| } from '../util/types'; | |
| import { CategoryAxisBaseOption } from '../coord/axisCommonTypes'; | |
| import { isArray, map, isObject, isString } from 'zrender/src/core/util'; | |
| type OrdinalScaleSetting = { | |
| ordinalMeta?: OrdinalMeta | CategoryAxisBaseOption['data']; | |
| extent?: [number, number]; | |
| }; | |
| class OrdinalScale extends Scale<OrdinalScaleSetting> { | |
| static type = 'ordinal'; | |
| readonly type = 'ordinal'; | |
| private _ordinalMeta: OrdinalMeta; | |
| /** | |
| * For example: | |
| * Given original ordinal data: | |
| * ```js | |
| * option = { | |
| * xAxis: { | |
| * // Their raw ordinal numbers are: | |
| * // 0 1 2 3 4 5 | |
| * data: ['a', 'b', 'c', 'd', 'e', 'f'] | |
| * }, | |
| * yAxis: {} | |
| * series: { | |
| * type: 'bar', | |
| * data: [ | |
| * ['d', 110], // ordinalNumber: 3 | |
| * ['c', 660], // ordinalNumber: 2 | |
| * ['f', 220], // ordinalNumber: 5 | |
| * ['e', 550] // ordinalNumber: 4 | |
| * ], | |
| * realtimeSort: true | |
| * } | |
| * }; | |
| * ``` | |
| * After realtime sorted (order by yValue desc): | |
| * ```js | |
| * _ordinalNumbersByTick: [ | |
| * 2, // tick: 0, yValue: 660 | |
| * 5, // tick: 1, yValue: 220 | |
| * 3, // tick: 2, yValue: 110 | |
| * 4, // tick: 3, yValue: 550 | |
| * 0, // tick: 4, yValue: - | |
| * 1, // tick: 5, yValue: - | |
| * ], | |
| * _ticksByOrdinalNumber: [ | |
| * 4, // ordinalNumber: 0, yValue: - | |
| * 5, // ordinalNumber: 1, yValue: - | |
| * 0, // ordinalNumber: 2, yValue: 660 | |
| * 2, // ordinalNumber: 3, yValue: 110 | |
| * 3, // ordinalNumber: 4, yValue: 550 | |
| * 1, // ordinalNumber: 5, yValue: 220 | |
| * ] | |
| * ``` | |
| * The index of this array is from `0` to `ordinalMeta.categories.length`. | |
| * | |
| * @see `Ordinal['getRawOrdinalNumber']` | |
| * @see `OrdinalSortInfo` | |
| */ | |
| private _ordinalNumbersByTick: OrdinalNumber[]; | |
| /** | |
| * This is the inverted map of `_ordinalNumbersByTick`. | |
| * The index of this array is from `0` to `ordinalMeta.categories.length`. | |
| * | |
| * @see `Ordinal['_ordinalNumbersByTick']` | |
| * @see `Ordinal['_getTickNumber']` | |
| * @see `OrdinalSortInfo` | |
| */ | |
| private _ticksByOrdinalNumber: number[]; | |
| constructor(setting?: OrdinalScaleSetting) { | |
| super(setting); | |
| let ordinalMeta = this.getSetting('ordinalMeta'); | |
| // Caution: Should not use instanceof, consider ec-extensions using | |
| // import approach to get OrdinalMeta class. | |
| if (!ordinalMeta) { | |
| ordinalMeta = new OrdinalMeta({}); | |
| } | |
| if (isArray(ordinalMeta)) { | |
| ordinalMeta = new OrdinalMeta({ | |
| categories: map(ordinalMeta, item => (isObject(item) ? item.value : item)) | |
| }); | |
| } | |
| this._ordinalMeta = ordinalMeta as OrdinalMeta; | |
| this._extent = this.getSetting('extent') || [0, ordinalMeta.categories.length - 1]; | |
| } | |
| parse(val: OrdinalRawValue | OrdinalNumber): OrdinalNumber { | |
| // Caution: Math.round(null) will return `0` rather than `NaN` | |
| if (val == null) { | |
| return NaN; | |
| } | |
| return isString(val) | |
| ? this._ordinalMeta.getOrdinal(val) | |
| // val might be float. | |
| : Math.round(val); | |
| } | |
| contain(rank: OrdinalRawValue | OrdinalNumber): boolean { | |
| rank = this.parse(rank); | |
| return scaleHelper.contain(rank, this._extent) | |
| && this._ordinalMeta.categories[rank] != null; | |
| } | |
| /** | |
| * Normalize given rank or name to linear [0, 1] | |
| * @param val raw ordinal number. | |
| * @return normalized value in [0, 1]. | |
| */ | |
| normalize(val: OrdinalRawValue | OrdinalNumber): number { | |
| val = this._getTickNumber(this.parse(val)); | |
| return scaleHelper.normalize(val, this._extent); | |
| } | |
| /** | |
| * @param val normalized value in [0, 1]. | |
| * @return raw ordinal number. | |
| */ | |
| scale(val: number): OrdinalNumber { | |
| val = Math.round(scaleHelper.scale(val, this._extent)); | |
| return this.getRawOrdinalNumber(val); | |
| } | |
| getTicks(): OrdinalScaleTick[] { | |
| const ticks = []; | |
| const extent = this._extent; | |
| let rank = extent[0]; | |
| while (rank <= extent[1]) { | |
| ticks.push({ | |
| value: rank | |
| }); | |
| rank++; | |
| } | |
| return ticks; | |
| } | |
| getMinorTicks(splitNumber: number): number[][] { | |
| // Not support. | |
| return; | |
| } | |
| /** | |
| * @see `Ordinal['_ordinalNumbersByTick']` | |
| */ | |
| setSortInfo(info: OrdinalSortInfo): void { | |
| if (info == null) { | |
| this._ordinalNumbersByTick = this._ticksByOrdinalNumber = null; | |
| return; | |
| } | |
| const infoOrdinalNumbers = info.ordinalNumbers; | |
| const ordinalsByTick = this._ordinalNumbersByTick = [] as OrdinalNumber[]; | |
| const ticksByOrdinal = this._ticksByOrdinalNumber = [] as number[]; | |
| // Unnecessary support negative tick in `realtimeSort`. | |
| let tickNum = 0; | |
| const allCategoryLen = this._ordinalMeta.categories.length; | |
| for (const len = Math.min(allCategoryLen, infoOrdinalNumbers.length); tickNum < len; ++tickNum) { | |
| const ordinalNumber = infoOrdinalNumbers[tickNum]; | |
| ordinalsByTick[tickNum] = ordinalNumber; | |
| ticksByOrdinal[ordinalNumber] = tickNum; | |
| } | |
| // Handle that `series.data` only covers part of the `axis.category.data`. | |
| let unusedOrdinal = 0; | |
| for (; tickNum < allCategoryLen; ++tickNum) { | |
| while (ticksByOrdinal[unusedOrdinal] != null) { | |
| unusedOrdinal++; | |
| }; | |
| ordinalsByTick.push(unusedOrdinal); | |
| ticksByOrdinal[unusedOrdinal] = tickNum; | |
| } | |
| } | |
| private _getTickNumber(ordinal: OrdinalNumber): number { | |
| const ticksByOrdinalNumber = this._ticksByOrdinalNumber; | |
| // also support ordinal out of range of `ordinalMeta.categories.length`, | |
| // where ordinal numbers are used as tick value directly. | |
| return (ticksByOrdinalNumber && ordinal >= 0 && ordinal < ticksByOrdinalNumber.length) | |
| ? ticksByOrdinalNumber[ordinal] | |
| : ordinal; | |
| } | |
| /** | |
| * @usage | |
| * ```js | |
| * const ordinalNumber = ordinalScale.getRawOrdinalNumber(tickVal); | |
| * | |
| * // case0 | |
| * const rawOrdinalValue = axisModel.getCategories()[ordinalNumber]; | |
| * // case1 | |
| * const rawOrdinalValue = this._ordinalMeta.categories[ordinalNumber]; | |
| * // case2 | |
| * const coord = axis.dataToCoord(ordinalNumber); | |
| * ``` | |
| * | |
| * @param {OrdinalNumber} tickNumber index of display | |
| */ | |
| getRawOrdinalNumber(tickNumber: number): OrdinalNumber { | |
| const ordinalNumbersByTick = this._ordinalNumbersByTick; | |
| // tickNumber may be out of range, e.g., when axis max is larger than `ordinalMeta.categories.length`., | |
| // where ordinal numbers are used as tick value directly. | |
| return (ordinalNumbersByTick && tickNumber >= 0 && tickNumber < ordinalNumbersByTick.length) | |
| ? ordinalNumbersByTick[tickNumber] | |
| : tickNumber; | |
| } | |
| /** | |
| * Get item on tick | |
| */ | |
| getLabel(tick: ScaleTick): string { | |
| if (!this.isBlank()) { | |
| const ordinalNumber = this.getRawOrdinalNumber(tick.value); | |
| const cateogry = this._ordinalMeta.categories[ordinalNumber]; | |
| // Note that if no data, ordinalMeta.categories is an empty array. | |
| // Return empty if it's not exist. | |
| return cateogry == null ? '' : cateogry + ''; | |
| } | |
| } | |
| count(): number { | |
| return this._extent[1] - this._extent[0] + 1; | |
| } | |
| unionExtentFromData(data: SeriesData, dim: DimensionLoose) { | |
| this.unionExtent(data.getApproximateExtent(dim)); | |
| } | |
| /** | |
| * @override | |
| * If value is in extent range | |
| */ | |
| isInExtentRange(value: OrdinalNumber): boolean { | |
| value = this._getTickNumber(value); | |
| return this._extent[0] <= value && this._extent[1] >= value; | |
| } | |
| getOrdinalMeta(): OrdinalMeta { | |
| return this._ordinalMeta; | |
| } | |
| calcNiceTicks() {} | |
| calcNiceExtent() {} | |
| } | |
| Scale.registerClass(OrdinalScale); | |
| export default OrdinalScale; | |