LoCs / echarts-5.5.0-rc.2 /src /data /helper /dataValueHelper.ts
ZoeDuan's picture
Upload 1382 files
4bb817b verified
raw
history blame
9.61 kB
/*
* 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 { ParsedValue, DimensionType } from '../../util/types';
import { parseDate, numericToNumber } from '../../util/number';
import { createHashMap, trim, hasOwn, isString, isNumber } from 'zrender/src/core/util';
import { throwError } from '../../util/log';
/**
* Convert raw the value in to inner value in List.
*
* [Performance sensitive]
*
* [Caution]: this is the key logic of user value parser.
* For backward compatibility, do not modify it until you have to!
*/
export function parseDataValue(
value: any,
// For high performance, do not omit the second param.
opt: {
// Default type: 'number'. There is no 'unknown' type. That is, a string
// will be parsed to NaN if do not set `type` as 'ordinal'. It has been
// the logic in `List.ts` for long time. Follow the same way if you need
// to get same result as List did from a raw value.
type?: DimensionType
}
): ParsedValue {
// Performance sensitive.
const dimType = opt && opt.type;
if (dimType === 'ordinal') {
// If given value is a category string
return value;
}
if (dimType === 'time'
// spead up when using timestamp
&& !isNumber(value)
&& value != null
&& value !== '-'
) {
value = +parseDate(value);
}
// dimType defaults 'number'.
// If dimType is not ordinal and value is null or undefined or NaN or '-',
// parse to NaN.
// number-like string (like ' 123 ') can be converted to a number.
// where null/undefined or other string will be converted to NaN.
return (value == null || value === '')
? NaN
// If string (like '-'), using '+' parse to NaN
// If object, also parse to NaN
: +value;
};
export type RawValueParserType = 'number' | 'time' | 'trim';
type RawValueParser = (val: unknown) => unknown;
const valueParserMap = createHashMap<RawValueParser, RawValueParserType>({
'number': function (val): number {
// Do not use `numericToNumber` here. We have `numericToNumber` by default.
// Here the number parser can have loose rule:
// enable to cut suffix: "120px" => 120, "14%" => 14.
return parseFloat(val as string);
},
'time': function (val): number {
// return timestamp.
return +parseDate(val);
},
'trim': function (val) {
return isString(val) ? trim(val) : val;
}
});
export function getRawValueParser(type: RawValueParserType): RawValueParser {
return valueParserMap.get(type);
}
export interface FilterComparator {
evaluate(val: unknown): boolean;
}
const ORDER_COMPARISON_OP_MAP: {
[key in OrderRelationOperator]: ((lval: unknown, rval: unknown) => boolean)
} = {
lt: (lval, rval) => lval < rval,
lte: (lval, rval) => lval <= rval,
gt: (lval, rval) => lval > rval,
gte: (lval, rval) => lval >= rval
};
class FilterOrderComparator implements FilterComparator {
private _rvalFloat: number;
private _opFn: (lval: unknown, rval: unknown) => boolean;
constructor(op: OrderRelationOperator, rval: unknown) {
if (!isNumber(rval)) {
let errMsg = '';
if (__DEV__) {
errMsg = 'rvalue of "<", ">", "<=", ">=" can only be number in filter.';
}
throwError(errMsg);
}
this._opFn = ORDER_COMPARISON_OP_MAP[op];
this._rvalFloat = numericToNumber(rval);
}
// Performance sensitive.
evaluate(lval: unknown): boolean {
// Most cases is 'number', and typeof maybe 10 times faseter than parseFloat.
return isNumber(lval)
? this._opFn(lval, this._rvalFloat)
: this._opFn(numericToNumber(lval), this._rvalFloat);
}
}
export class SortOrderComparator {
private _incomparable: number;
private _resultLT: -1 | 1;
/**
* @param order by default: 'asc'
* @param incomparable by default: Always on the tail.
* That is, if 'asc' => 'max', if 'desc' => 'min'
* See the definition of "incomparable" in [SORT_COMPARISON_RULE].
*/
constructor(order: 'asc' | 'desc', incomparable: 'min' | 'max') {
const isDesc = order === 'desc';
this._resultLT = isDesc ? 1 : -1;
if (incomparable == null) {
incomparable = isDesc ? 'min' : 'max';
}
this._incomparable = incomparable === 'min' ? -Infinity : Infinity;
}
// See [SORT_COMPARISON_RULE].
// Performance sensitive.
evaluate(lval: unknown, rval: unknown): -1 | 0 | 1 {
// Most cases is 'number', and typeof maybe 10 times faseter than parseFloat.
let lvalFloat = isNumber(lval) ? lval : numericToNumber(lval);
let rvalFloat = isNumber(rval) ? rval : numericToNumber(rval);
const lvalNotNumeric = isNaN(lvalFloat as number);
const rvalNotNumeric = isNaN(rvalFloat as number);
if (lvalNotNumeric) {
lvalFloat = this._incomparable;
}
if (rvalNotNumeric) {
rvalFloat = this._incomparable;
}
if (lvalNotNumeric && rvalNotNumeric) {
const lvalIsStr = isString(lval);
const rvalIsStr = isString(rval);
if (lvalIsStr) {
lvalFloat = rvalIsStr ? lval as unknown as number : 0;
}
if (rvalIsStr) {
rvalFloat = lvalIsStr ? rval as unknown as number : 0;
}
}
return lvalFloat < rvalFloat ? this._resultLT
: lvalFloat > rvalFloat ? (-this._resultLT as -1 | 1)
: 0;
}
}
class FilterEqualityComparator implements FilterComparator {
private _isEQ: boolean;
private _rval: unknown;
private _rvalTypeof: string;
private _rvalFloat: number;
constructor(isEq: boolean, rval: unknown) {
this._rval = rval;
this._isEQ = isEq;
this._rvalTypeof = typeof rval;
this._rvalFloat = numericToNumber(rval);
}
// Performance sensitive.
evaluate(lval: unknown): boolean {
let eqResult = lval === this._rval;
if (!eqResult) {
const lvalTypeof = typeof lval;
if (lvalTypeof !== this._rvalTypeof && (lvalTypeof === 'number' || this._rvalTypeof === 'number')) {
eqResult = numericToNumber(lval) === this._rvalFloat;
}
}
return this._isEQ ? eqResult : !eqResult;
}
}
type OrderRelationOperator = 'lt' | 'lte' | 'gt' | 'gte';
export type RelationalOperator = OrderRelationOperator | 'eq' | 'ne';
/**
* [FILTER_COMPARISON_RULE]
* `lt`|`lte`|`gt`|`gte`:
* + rval must be a number. And lval will be converted to number (`numericToNumber`) to compare.
* `eq`:
* + If same type, compare with `===`.
* + If there is one number, convert to number (`numericToNumber`) to compare.
* + Else return `false`.
* `ne`:
* + Not `eq`.
*
*
* [SORT_COMPARISON_RULE]
* All the values are grouped into three categories:
* + "numeric" (number and numeric string)
* + "non-numeric-string" (string that excluding numeric string)
* + "others"
* "numeric" vs "numeric": values are ordered by number order.
* "non-numeric-string" vs "non-numeric-string": values are ordered by ES spec (#sec-abstract-relational-comparison).
* "others" vs "others": do not change order (always return 0).
* "numeric" vs "non-numeric-string": "non-numeric-string" is treated as "incomparable".
* "number" vs "others": "others" is treated as "incomparable".
* "non-numeric-string" vs "others": "others" is treated as "incomparable".
* "incomparable" will be seen as -Infinity or Infinity (depends on the settings).
* MEMO:
* Non-numeric string sort makes sense when we need to put the items with the same tag together.
* But if we support string sort, we still need to avoid the misleading like `'2' > '12'`,
* So we treat "numeric-string" sorted by number order rather than string comparison.
*
*
* [CHECK_LIST_OF_THE_RULE_DESIGN]
* + Do not support string comparison until required. And also need to
* avoid the misleading of "2" > "12".
* + Should avoid the misleading case:
* `" 22 " gte "22"` is `true` but `" 22 " eq "22"` is `false`.
* + JS bad case should be avoided: null <= 0, [] <= 0, ' ' <= 0, ...
* + Only "numeric" can be converted to comparable number, otherwise converted to NaN.
* See `util/number.ts#numericToNumber`.
*
* @return If `op` is not `RelationalOperator`, return null;
*/
export function createFilterComparator(
op: string,
rval?: unknown
): FilterComparator {
return (op === 'eq' || op === 'ne')
? new FilterEqualityComparator(op === 'eq', rval)
: hasOwn(ORDER_COMPARISON_OP_MAP, op)
? new FilterOrderComparator(op as OrderRelationOperator, rval)
: null;
}