You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2020/08/14 04:35:29 UTC
[incubator-echarts] 02/02: fix: [data-transform] (1) clarity the
detail of value comparison. (2) rename "parse" to "parser".
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch dataset-trans2
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git
commit 81d1306b5fc7dc528b01a360fe042b2b2ed72736
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Aug 14 05:09:54 2020 +0800
fix: [data-transform] (1) clarity the detail of value comparison. (2) rename "parse" to "parser".
---
src/component/transform/sortTransform.ts | 59 ++--
src/data/helper/dataValueHelper.ts | 206 ++++++------
src/util/conditionalExpression.ts | 32 +-
src/util/number.ts | 15 +-
test/data-transform.html | 173 ++++++++--
test/ut/spec/data/dataValueHelper.test.js | 531 ++++++++++++++++++++----------
6 files changed, 662 insertions(+), 354 deletions(-)
diff --git a/src/component/transform/sortTransform.ts b/src/component/transform/sortTransform.ts
index cd47616..8f6f184 100644
--- a/src/component/transform/sortTransform.ts
+++ b/src/component/transform/sortTransform.ts
@@ -22,10 +22,10 @@ import {
DimensionLoose, SOURCE_FORMAT_KEYED_COLUMNS, DimensionIndex, OptionDataValue
} from '../../util/types';
import { makePrintable, throwError } from '../../util/log';
-import { isArray, each, hasOwn } from 'zrender/src/core/util';
+import { isArray, each } from 'zrender/src/core/util';
import { normalizeToArray } from '../../util/model';
import {
- RawValueParserType, getRawValueParser, createRelationalComparator
+ RawValueParserType, getRawValueParser, SortOrderComparator
} from '../../data/helper/dataValueHelper';
/**
@@ -54,12 +54,13 @@ export interface SortTransformOption extends DataTransformOption {
// PENDING: whether support { dimension: 'score', order: 'asc' } ?
type OrderExpression = {
dimension: DimensionLoose;
- order: SortOrder;
- parse?: RawValueParserType;
+ order: 'asc' | 'desc';
+ parser?: RawValueParserType;
+ // Value that is not comparable (like null/undefined) will be
+ // put to head or tail.
+ incomparable?: 'min' | 'max';
};
-type SortOrder = 'asc' | 'desc';
-const SortOrderValidMap = { asc: true, desc: true } as const;
let sampleLog = '';
if (__DEV__) {
@@ -95,13 +96,14 @@ export const sortTransform: ExternalDataTransform<SortTransformOption> = {
const orderDefList: {
dimIdx: DimensionIndex;
- orderReturn: -1 | 1;
parser: ReturnType<typeof getRawValueParser>;
+ comparator: SortOrderComparator
}[] = [];
each(orderExprList, function (orderExpr) {
const dimLoose = orderExpr.dimension;
const order = orderExpr.order;
- const parserName = orderExpr.parse;
+ const parserName = orderExpr.parser;
+ const incomparable = orderExpr.incomparable;
if (dimLoose == null) {
if (__DEV__) {
@@ -110,13 +112,28 @@ export const sortTransform: ExternalDataTransform<SortTransformOption> = {
throwError(errMsg);
}
- if (!hasOwn(SortOrderValidMap, order)) {
+ if (order !== 'asc' && order !== 'desc') {
if (__DEV__) {
errMsg = 'Sort transform config must has "order" specified.' + sampleLog;
}
throwError(errMsg);
}
+ if (incomparable && (incomparable !== 'min' && incomparable !== 'max')) {
+ let errMsg = '';
+ if (__DEV__) {
+ errMsg = 'incomparable must be "min" or "max" rather than "' + incomparable + '".';
+ }
+ throwError(errMsg);
+ }
+ if (order !== 'asc' && order !== 'desc') {
+ let errMsg = '';
+ if (__DEV__) {
+ errMsg = 'order must be "asc" or "desc" rather than "' + order + '".';
+ }
+ throwError(errMsg);
+ }
+
const dimInfo = source.getDimensionInfo(dimLoose);
if (!dimInfo) {
if (__DEV__) {
@@ -142,8 +159,8 @@ export const sortTransform: ExternalDataTransform<SortTransformOption> = {
orderDefList.push({
dimIdx: dimInfo.index,
- orderReturn: order === 'asc' ? -1 : 1,
- parser: parser
+ parser: parser,
+ comparator: new SortOrderComparator(order, incomparable)
});
});
@@ -170,9 +187,6 @@ export const sortTransform: ExternalDataTransform<SortTransformOption> = {
resultData.push(source.getRawDataItem(i));
}
- const lt = createRelationalComparator('lt');
- const gt = createRelationalComparator('gt');
-
resultData.sort(function (item0, item1) {
if (item0 === headerPlaceholder) {
return -1;
@@ -180,15 +194,6 @@ export const sortTransform: ExternalDataTransform<SortTransformOption> = {
if (item1 === headerPlaceholder) {
return 1;
}
- // FIXME: check other empty?
- // Always put empty item last?
- if (item0 == null) {
- return 1;
- }
- if (item1 == null) {
- return -1;
- }
- // TODO Optimize a little: manually loop unrolling?
for (let i = 0; i < orderDefList.length; i++) {
const orderDef = orderDefList[i];
let val0 = source.retrieveItemValue(item0, orderDef.dimIdx);
@@ -197,11 +202,9 @@ export const sortTransform: ExternalDataTransform<SortTransformOption> = {
val0 = orderDef.parser(val0) as OptionDataValue;
val1 = orderDef.parser(val1) as OptionDataValue;
}
- if (lt.evaluate(val0, val1)) {
- return orderDef.orderReturn;
- }
- else if (gt.evaluate(val0, val1)) {
- return -orderDef.orderReturn;
+ const result = orderDef.comparator.evaluate(val0, val1);
+ if (result !== 0) {
+ return result;
}
}
return 0;
diff --git a/src/data/helper/dataValueHelper.ts b/src/data/helper/dataValueHelper.ts
index 9214bb3..07c98e0 100644
--- a/src/data/helper/dataValueHelper.ts
+++ b/src/data/helper/dataValueHelper.ts
@@ -21,6 +21,7 @@ import { ParsedValue, DimensionType } from '../../util/types';
import OrdinalMeta from '../OrdinalMeta';
import { parseDate, numericToNumber } from '../../util/number';
import { createHashMap, trim, hasOwn } from 'zrender/src/core/util';
+import { throwError } from '../../util/log';
/**
@@ -99,51 +100,99 @@ export function getRawValueParser(type: RawValueParserType): RawValueParser {
-export interface UnaryExpression {
- evaluate(val: unknown): unknown;
-}
-export interface BinaryExpression {
- evaluate(lval: unknown, rval: unknown): unknown;
+export interface FilterComparator {
+ evaluate(val: unknown): boolean;
}
-class OrderComparatorUnary implements UnaryExpression {
- _rval: unknown;
- _rvalTypeof: string; // typeof rval
- _rvalFloat: number;
- _rvalIsNumeric: boolean;
- _opFn: (lval: unknown, rval: 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 (typeof rval !== 'number') {
+ 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.
- const lvalIsNumber = typeof lval === 'number';
- return (lvalIsNumber && this._rvalIsNumeric)
+ return typeof lval === 'number'
? this._opFn(lval, this._rvalFloat)
- : (lvalIsNumber || this._rvalTypeof === 'number')
- ? this._opFn(numericToNumber(lval), this._rvalFloat)
- : false;
+ : this._opFn(numericToNumber(lval), this._rvalFloat);
}
}
-class OrderComparatorBinary implements BinaryExpression {
- _opFn: (lval: unknown, rval: unknown) => boolean;
+
+export class SortOrderComparator {
+ private _incomparable: number;
+ private _resultLT: -1 | 1;
+ /**
+ * @param order by defualt: 'asc'
+ * @param incomparable by defualt: Always on the tail.
+ * That is, if 'asc' => 'max', if 'desc' => 'min'
+ */
+ 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;
+ }
// Performance sensitive.
- evaluate(lval: unknown, rval: unknown): boolean {
+ evaluate(lval: unknown, rval: unknown): -1 | 0 | 1 {
// Most cases is 'number', and typeof maybe 10 times faseter than parseFloat.
- const lvalIsNumber = typeof lval === 'number';
- const rvalIsNumber = typeof rval === 'number';
- return (lvalIsNumber && rvalIsNumber)
- ? this._opFn(lval, rval)
- : (lvalIsNumber || rvalIsNumber)
- ? this._opFn(numericToNumber(lval), numericToNumber(rval))
- : false;
+ const lvalTypeof = typeof lval;
+ const rvalTypeof = typeof rval;
+ let lvalFloat = lvalTypeof === 'number' ? lval : numericToNumber(lval);
+ let rvalFloat = rvalTypeof === 'number' ? rval : numericToNumber(rval);
+ const lvalIncmpr = isNaN(lvalFloat as number);
+ const rvalIncmpr = isNaN(rvalFloat as number);
+ if (lvalIncmpr) {
+ lvalFloat = this._incomparable;
+ }
+ if (rvalIncmpr) {
+ rvalFloat = this._incomparable;
+ }
+ // In most cases, pure string sort has no meanings. But it can exists when need to
+ // group two categories (and order by anthor dimension meanwhile).
+ // But if we support string sort, we still need to avoid the misleading of `'2' > '12'`,
+ // and support '-' means empty, and trade `'abc' > 2` as incomparable.
+ // So we support string comparison only if both lval and rval are string and not numeric.
+ if (lvalIncmpr && rvalIncmpr && lvalTypeof === 'string' && rvalTypeof === 'string') {
+ lvalFloat = lval;
+ rvalFloat = rval;
+ }
+ return lvalFloat < rvalFloat ? this._resultLT
+ : lvalFloat > rvalFloat ? (-this._resultLT as -1 | 1)
+ : 0;
}
}
-class EqualityComparatorUnary implements UnaryExpression {
- _rval: unknown;
- _rvalTypeof: string; // typeof rval
- _rvalFloat: number;
- _rvalIsNumeric: boolean;
- _isEq: boolean;
+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;
@@ -153,80 +202,47 @@ class EqualityComparatorUnary implements UnaryExpression {
eqResult = numericToNumber(lval) === this._rvalFloat;
}
}
- return this._isEq ? eqResult : !eqResult;
+ return this._isEQ ? eqResult : !eqResult;
}
}
-class EqualityComparatorBinary implements BinaryExpression {
- _isEq: boolean;
- // Performance sensitive.
- evaluate(lval: unknown, rval: unknown): boolean {
- let eqResult = lval === rval;
- if (!eqResult) {
- const lvalTypeof = typeof lval;
- const rvalTypeof = typeof rval;
- if (lvalTypeof !== rvalTypeof && (lvalTypeof === 'number' || rvalTypeof === 'number')) {
- eqResult = numericToNumber(lval) === numericToNumber(rval);
- }
- }
- return this._isEq ? eqResult : !eqResult;
- }
-}
-
-const ORDER_COMPARISON_OP_MAP = {
- lt: (tarVal: unknown, condVal: unknown) => tarVal < condVal,
- lte: (tarVal: unknown, condVal: unknown) => tarVal <= condVal,
- gt: (tarVal: unknown, condVal: unknown) => tarVal > condVal,
- gte: (tarVal: unknown, condVal: unknown) => tarVal >= condVal
-} as const;
-
-export type RelationalOperator = 'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne';
+type OrderRelationOperator = 'lt' | 'lte' | 'gt' | 'gte';
+export type RelationalOperator = OrderRelationOperator | 'eq' | 'ne';
/**
- * [COMPARISON_RULE]
- * `lt`, `lte`, `gt`, `gte`:
- * + If two "number" or a "number" and a "numeric": convert to number and compare.
- * + Else return `false`.
+ * [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 two "number" or a "number" and a "numeric": convert to number and compare.
+ * + If same type, compare with `===`.
+ * + If there is one number, convert to number (`numericToNumber`) to compare.
* + Else return `false`.
* `ne`:
* + Not `eq`.
*
- * Definition of "numeric": see `util/number.ts#numericToNumber`.
+ * [SORT_COMPARISON_RULE]
+ * Only `lt`|`gt`.
+ * Always convert to number (`numericToNumer`) to compare.
+ * (e.g., consider case: [12, " 13 ", " 14 ", null, 15])
+ *
+ * [CHECK_LIST_OF_THE_RULE_DESIGN]
+ * + Do not support string comparison until required. And also need to
+ * void 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`.
*
- * [MEMO]
- * + Do not support string comparison until required. And also need to consider the
- * misleading of "2" > "12".
- * + JS bad case considered: null <= 0, [] <= 0, ' ' <= 0, ...
+ * @return If `op` is not `RelationalOperator`, return null;
*/
-export function createRelationalComparator(op: RelationalOperator): BinaryExpression;
-export function createRelationalComparator(op: RelationalOperator, isUnary: true, rval: unknown): UnaryExpression;
-export function createRelationalComparator(
- op: RelationalOperator,
- isUnary?: true,
+export function createFilterComparator(
+ op: string,
rval?: unknown
-): UnaryExpression | BinaryExpression {
- let comparator;
- if (op === 'eq' || op === 'ne') {
- comparator = isUnary ? new EqualityComparatorUnary() : new EqualityComparatorBinary();
- comparator._isEq = op === 'eq';
- }
- else {
- comparator = isUnary ? new OrderComparatorUnary() : new OrderComparatorBinary();
- comparator._opFn = ORDER_COMPARISON_OP_MAP[op];
- }
- if (isUnary) {
- const unaryComp = comparator as OrderComparatorUnary | EqualityComparatorUnary;
- unaryComp._rval = rval;
- unaryComp._rvalTypeof = typeof rval;
- const rvalFloat = unaryComp._rvalFloat = numericToNumber(rval);
- unaryComp._rvalIsNumeric = !isNaN(rvalFloat); // eslint-disable-line eqeqeq
- }
- return comparator;
-}
-
-export function isRelationalOperator(op: string): op is RelationalOperator {
- return hasOwn(ORDER_COMPARISON_OP_MAP, op) || op === 'eq' || op === 'ne';
+): FilterComparator {
+ return (op === 'eq' || op === 'ne')
+ ? new FilterEqualityComparator(op === 'eq', rval)
+ : hasOwn(ORDER_COMPARISON_OP_MAP, op)
+ ? new FilterOrderComparator(op as OrderRelationOperator, rval)
+ : null;
}
diff --git a/src/util/conditionalExpression.ts b/src/util/conditionalExpression.ts
index b871b09..2d9b752 100644
--- a/src/util/conditionalExpression.ts
+++ b/src/util/conditionalExpression.ts
@@ -23,13 +23,13 @@ import {
} from 'zrender/src/core/util';
import { throwError, makePrintable } from './log';
import {
- RawValueParserType, getRawValueParser, isRelationalOperator,
- createRelationalComparator, RelationalOperator, UnaryExpression
+ RawValueParserType, getRawValueParser,
+ RelationalOperator, FilterComparator, createFilterComparator
} from '../data/helper/dataValueHelper';
// PENDING:
-// (1) Support more parser like: `parse: 'trim'`, `parse: 'lowerCase'`, `parse: 'year'`, `parse: 'dayOfWeek'`?
+// (1) Support more parser like: `parser: 'trim'`, `parser: 'lowerCase'`, `parser: 'year'`, `parser: 'dayOfWeek'`?
// (2) Support piped parser ?
// (3) Support callback parser or callback condition?
// (4) At present do not support string expression yet but only stuctured expression.
@@ -77,24 +77,24 @@ import {
* ```js
* // Trim if string
* {
- * parse: 'trim',
+ * parser: 'trim',
* eq: 'Flowers'
* }
* // Parse as time and enable arithmetic relation comparison.
* {
- * parse: 'time',
+ * parser: 'time',
* lt: '2012-12-12'
* }
* // Normalize number-like string and make '-' to Null.
* {
- * parse: 'time',
+ * parser: 'time',
* lt: '2012-12-12'
* }
* // Normalize to number:
* // + number-like string (like ' 123 ') can be converted to a number.
* // + where null/undefined or other string will be converted to NaN.
* {
- * parse: 'number',
+ * parser: 'number',
* eq: 2011
* }
* // RegExp, include the feature in SQL: `like '%xxx%'`.
@@ -164,13 +164,13 @@ type RelationalExpressionOptionByOpAlias = Record<keyof typeof RELATIONAL_EXPRES
interface RelationalExpressionOption extends
RelationalExpressionOptionByOp, RelationalExpressionOptionByOpAlias {
dimension?: DimensionLoose;
- parse?: RawValueParserType;
+ parser?: RawValueParserType;
}
type RelationalExpressionOpEvaluate = (tarVal: unknown, condVal: unknown) => boolean;
-class RegExpEvaluator implements UnaryExpression {
+class RegExpEvaluator implements FilterComparator {
private _condVal: RegExp;
constructor(rVal: unknown) {
@@ -279,7 +279,7 @@ class RelationalConditionInternal implements ParsedConditionInternal {
valueParser: ReturnType<typeof getRawValueParser>;
// If no parser, be null/undefined.
getValue: ConditionalExpressionValueGetter;
- subCondList: UnaryExpression[];
+ subCondList: FilterComparator[];
evaluate() {
const needParse = !!this.valueParser;
@@ -392,12 +392,12 @@ function parseRelationalOption(
const subCondList = [] as RelationalConditionInternal['subCondList'];
const exprKeys = keys(exprOption);
- const parserName = exprOption.parse;
+ const parserName = exprOption.parser;
const valueParser = parserName ? getRawValueParser(parserName) : null;
for (let i = 0; i < exprKeys.length; i++) {
const keyRaw = exprKeys[i];
- if (keyRaw === 'parse' || getters.valueGetterAttrMap.get(keyRaw)) {
+ if (keyRaw === 'parser' || getters.valueGetterAttrMap.get(keyRaw)) {
continue;
}
@@ -406,12 +406,8 @@ function parseRelationalOption(
: (keyRaw as keyof RelationalExpressionOptionByOp);
const condValueRaw = exprOption[keyRaw];
const condValueParsed = valueParser ? valueParser(condValueRaw) : condValueRaw;
- const evaluator =
- isRelationalOperator(op)
- ? createRelationalComparator(op, true, condValueParsed)
- : op === 'reg'
- ? new RegExpEvaluator(condValueParsed)
- : null;
+ const evaluator = createFilterComparator(op, condValueParsed)
+ || (op === 'reg' && new RegExpEvaluator(condValueParsed));
if (!evaluator) {
if (__DEV__) {
diff --git a/src/util/number.ts b/src/util/number.ts
index b2ae043..775d51a 100644
--- a/src/util/number.ts
+++ b/src/util/number.ts
@@ -549,22 +549,19 @@ export function reformIntervals(list: IntervalItem[]): IntervalItem[] {
* non-string, ...
*
* @test See full test cases in `test/ut/spec/util/number.js`.
+ * @return Must be a typeof number. If not numeric, return NaN.
*/
export function numericToNumber(val: unknown): number {
const valFloat = parseFloat(val as string);
- return isNumericHavingParseFloat(val, valFloat) ? valFloat : NaN;
+ return (
+ valFloat == val // eslint-disable-line eqeqeq
+ && (valFloat !== 0 || typeof val !== 'string' || val.indexOf('x') <= 0) // For case ' 0x0 '.
+ ) ? valFloat : NaN;
}
/**
* Definition of "numeric": see `numericToNumber`.
*/
export function isNumeric(val: unknown): val is number {
- return isNumericHavingParseFloat(val, parseFloat(val as string));
-}
-
-function isNumericHavingParseFloat(val: unknown, valFloat: number): val is number {
- return (
- valFloat == val // eslint-disable-line eqeqeq
- && (valFloat !== 0 || typeof val !== 'string' || val.indexOf('x') <= 0) // For case ' 0x0 '.
- );
+ return !isNaN(numericToNumber(val));
}
diff --git a/test/data-transform.html b/test/data-transform.html
index 9a11dc7..0f23efe 100644
--- a/test/data-transform.html
+++ b/test/data-transform.html
@@ -80,21 +80,24 @@ under the License.
Age: 1,
Sex: 2,
Score: 3,
- Date: 4
+ Date: 4,
+ DirtyNumber: 5,
+ Numeric: 6,
+ HasEmpty: 7
};
var NAME_SCORE_DIRTY_DATA_HEADER =
- ['Name', 'Age', 'Sex', 'Score', 'Date'];
+ ['Name', 'Age', 'Sex', 'Score', 'Date', 'DirtyNumber', 'Numeric', 'HasEmpty'];
var NAME_SCORE_DIRTY_DATA_NO_HEADER = [
// This is for trim testing.
- [' Jobs Mat ', 41, 'male', 314, '2011-02-12'],
+ [' Jobs Mat ', 41, 'male', 314, '2011-02-12', '13', ' 91000 ', 45 ],
// This is for edge testing (03-01, 20)
- ['Hottlyuipe Xu ', 20, 'female', 351, '2011-03-01'],
- [' Jone Mat ', 52, 'male', 287, '2011-02-14'],
- ['Uty Xu', 19, 'male', 219, '2011-02-18'],
- ['Tatum von Godden', 25, 'female', 301, '2011-04-02'],
- ['Must Godden', 31, 'female', 235, '2011-03-19'],
- ['Caoas Xu', 71, 'male', 318, '2011-02-24'],
- ['Malise Mat', 67, 'female', 366, '2011-03-12'],
+ ['Hottlyuipe Xu ', 20, 'female', 351, '2011-03-01', 44, ' 83000 ', 13 ],
+ [' Jone Mat ', 52, 'male', 287, '2011-02-14', null, ' 43000 ', null ],
+ ['Uty Xu', 19, 'male', 219, '2011-02-18', undefined, ' 63000 ', 81 ],
+ ['Tatum von Godden', 25, 'female', 301, '2011-04-02', '-', ' 13000 ', undefined ],
+ ['Must Godden', 31, 'female', 235, '2011-03-19', ' 454', '-', 32 ],
+ ['Caoas Xu', 71, 'male', 318, '2011-02-24', NaN, ' 73000 ', '-' ],
+ ['Malise Mat', 67, 'female', 366, '2011-03-12', '232a', ' 23000 ', 19 ]
];
var NAME_SCORE_DIRTY_DATA_WITH_HEADER =
[NAME_SCORE_DIRTY_DATA_HEADER]
@@ -287,7 +290,8 @@ under the License.
NAME_SCORE_DIM.Date,
NAME_SCORE_DIM.Score,
NAME_SCORE_DIM.Sex,
- NAME_SCORE_DIM.Age
+ NAME_SCORE_DIM.Age,
+ NAME_SCORE_DIM.DirtyNumber
]
};
option.series.push(series);
@@ -299,7 +303,7 @@ under the License.
transform: {
type: 'filter',
// print: true,
- config: { dimension: NAME_SCORE_DIM.Name, eq: 'Jobs Mat', parse: 'trim' }
+ config: { dimension: NAME_SCORE_DIM.Name, eq: 'Jobs Mat', parser: 'trim' }
}
});
addCartesian({
@@ -314,7 +318,7 @@ under the License.
transform: {
type: 'filter',
// print: true,
- config: { dimension: NAME_SCORE_DIM.Date, lt: '2011-03', gte: '2011-02', parse: 'time' }
+ config: { dimension: NAME_SCORE_DIM.Date, lt: '2011-03', gte: '2011-02', parser: 'time' }
}
});
addCartesian({
@@ -329,7 +333,7 @@ under the License.
transform: {
type: 'filter',
// print: true,
- config: { dimension: NAME_SCORE_DIM.Date, lte: '2011-03', gte: '2011-02-29', parse: 'time' }
+ config: { dimension: NAME_SCORE_DIM.Date, lte: '2011-03', gte: '2011-02-29', parser: 'time' }
}
});
addCartesian({
@@ -344,7 +348,7 @@ under the License.
transform: {
type: 'filter',
// print: true,
- config: { dimension: NAME_SCORE_DIM.Name, reg: /\sXu$/, parse: 'trim' }
+ config: { dimension: NAME_SCORE_DIM.Name, reg: /\sXu$/, parser: 'trim' }
}
});
addCartesian({
@@ -359,7 +363,7 @@ under the License.
transform: {
type: 'filter',
// print: true,
- config: { dimension: NAME_SCORE_DIM.Sex, ne: 'male', parse: 'trim' }
+ config: { dimension: NAME_SCORE_DIM.Sex, ne: 'male', parser: 'trim' }
}
});
addCartesian({
@@ -377,7 +381,7 @@ under the License.
// print: true,
config: {
and: [
- { dimension: NAME_SCORE_DIM.Sex, eq: 'male', parse: 'trim' },
+ { dimension: NAME_SCORE_DIM.Sex, eq: 'male', parser: 'trim' },
{ dimension: NAME_SCORE_DIM.Score, '>': 300 }
]
}
@@ -464,6 +468,30 @@ under the License.
});
+ option.dataset.push({
+ id: 'j',
+ transform: {
+ type: 'filter',
+ // print: true,
+ config: {
+ or: [{
+ dimension: NAME_SCORE_DIM.DirtyNumber,
+ eq: 454
+ }, {
+ dimension: NAME_SCORE_DIM.DirtyNumber,
+ eq: 232
+ }]
+ }
+ }
+ });
+ addCartesian({
+ series: {
+ datasetId: 'j',
+ encode: { label: [NAME_SCORE_DIM.DirtyNumber] }
+ },
+ xAxis: { name: 'Show only "Must Godden"' }
+ });
+
var chart = testHelper.create(echarts, 'main_cartesian_parse_trim_time_reg', {
@@ -501,7 +529,7 @@ under the License.
var leftStart = 50;
var leftBase = leftStart;
var topBase = 30;
- var gridWidth = 100;
+ var gridWidth = 200;
var gridHeight = 100;
var gapWidth = 70;
var gapHeight = 80;
@@ -537,7 +565,13 @@ under the License.
series.type = 'bar';
series.xAxisIndex = option.xAxis.length - 1;
series.yAxisIndex = option.yAxis.length - 1;
- series.label = { show: true, position: 'top' };
+ series.label = {
+ show: true,
+ position: 'insideBottom',
+ rotate: 90,
+ align: 'left',
+ verticalAlign: 'middle'
+ };
series.encode = {
x: NAME_SCORE_DIM.Date,
y: NAME_SCORE_DIM.Score,
@@ -547,7 +581,10 @@ under the License.
NAME_SCORE_DIM.Date,
NAME_SCORE_DIM.Score,
NAME_SCORE_DIM.Sex,
- NAME_SCORE_DIM.Age
+ NAME_SCORE_DIM.Age,
+ NAME_SCORE_DIM.DirtyNumber,
+ NAME_SCORE_DIM.Numeric,
+ NAME_SCORE_DIM.HasEmpty
]
};
option.series.push(series);
@@ -564,9 +601,10 @@ under the License.
});
addCartesian({
series: {
+ encode: { label: NAME_SCORE_DIM.Score },
datasetId: 'a'
},
- xAxis: { name: 'Show all eight\norder by Score asc' }
+ xAxis: { name: 'Show all eight bars\norder by Score asc' }
});
option.dataset.push({
@@ -582,7 +620,7 @@ under the License.
datasetId: 'b',
encode: { label: NAME_SCORE_DIM.Age }
},
- xAxis: { name: 'Show all eight\norder by Age desc' }
+ xAxis: { name: 'Show all eight bars\norder by Age desc' }
});
option.dataset.push({
@@ -599,9 +637,9 @@ under the License.
addCartesian({
series: {
datasetId: 'c',
- encode: { label: NAME_SCORE_DIM.Sex }
+ encode: { label: [NAME_SCORE_DIM.Sex, NAME_SCORE_DIM.Score] }
},
- xAxis: { name: 'Show all eight\nSex asc, Score desc' }
+ xAxis: { name: 'Show all eight bars\nSex asc (all female left)\nScore desc in each Sex' }
});
option.dataset.push({
@@ -610,15 +648,16 @@ under the License.
type: 'sort',
// print: true,
config: [
- { dimension: NAME_SCORE_DIM.Date, order: 'asc', parse: 'time' }
+ { dimension: NAME_SCORE_DIM.Date, order: 'asc', parser: 'time' }
]
}
});
addCartesian({
series: {
+ encode: { label: NAME_SCORE_DIM.Date },
datasetId: 'd'
},
- xAxis: { name: 'Show all eight\nDate asc' }
+ xAxis: { name: 'Show all eight bars\nDate asc' }
});
@@ -626,26 +665,98 @@ under the License.
id: 'e',
transform: [{
type: 'filter',
- // print: true,
config: { dimension: NAME_SCORE_DIM.Age, lte: 40, gte: 20 }
}, {
type: 'sort',
- // print: true,
config: { dimension: NAME_SCORE_DIM.Score, order: 'asc' }
}]
});
addCartesian({
series: {
+ encode: { label: [NAME_SCORE_DIM.Age, NAME_SCORE_DIM.Score] },
datasetId: 'e'
},
- xAxis: { name: 'Show three ponits\nFilter by Age 20-40\nOrder by Score' }
+ xAxis: { name: 'Show three bars\nFilter by Age 20-40\nOrder by Score asc' }
+ });
+
+
+ option.dataset.push({
+ id: 'f',
+ transform: {
+ type: 'sort',
+ config: [
+ { dimension: NAME_SCORE_DIM.DirtyNumber, order: 'desc', parser: 'number' }
+ ]
+ }
+ });
+ addCartesian({
+ series: {
+ encode: { label: NAME_SCORE_DIM.DirtyNumber },
+ datasetId: 'f'
+ },
+ xAxis: { name: 'Show all eight bars\nOrder by DirtyNumber desc' }
+ });
+
+
+ option.dataset.push({
+ id: 'g',
+ transform: {
+ type: 'sort',
+ config: [
+ { dimension: NAME_SCORE_DIM.Numeric, order: 'asc' }
+ ]
+ }
+ });
+ addCartesian({
+ series: {
+ encode: { label: NAME_SCORE_DIM.Numeric },
+ datasetId: 'g'
+ },
+ xAxis: { name: 'Show all eight bars\nOrder by Numeric asc\nOnly one empty at right' }
+ });
+
+
+ option.dataset.push({
+ id: 'h',
+ transform: {
+ type: 'sort',
+ config: [
+ { dimension: NAME_SCORE_DIM.HasEmpty, order: 'desc' }
+ ]
+ }
+ });
+ addCartesian({
+ series: {
+ encode: { label: NAME_SCORE_DIM.HasEmpty },
+ datasetId: 'h'
+ },
+ xAxis: { name: 'Show all eight bars\nOrder by HasEmpty desc\nempty at right' }
+ });
+
+
+ option.dataset.push({
+ id: 'i',
+ transform: {
+ type: 'sort',
+ config: [
+ { dimension: NAME_SCORE_DIM.HasEmpty, order: 'desc', incomparable: 'max' }
+ ]
+ }
+ });
+ addCartesian({
+ series: {
+ encode: { label: NAME_SCORE_DIM.HasEmpty },
+ datasetId: 'i'
+ },
+ xAxis: { name: 'Show all eight bars\nOrder by HasEmpty desc\nempty at left' }
});
var chart = testHelper.create(echarts, 'main_cartesian_sort', {
title: [
- 'Check each cartesians.',
- 'The expectationa are below each cartesian.'
+ 'Test sort transform. Check each cartesians.',
+ 'The expectationa are below each cartesian.',
+ 'Ordered dimension is on **bar label **'
],
width: chartWidth,
height: 600,
diff --git a/test/ut/spec/data/dataValueHelper.test.js b/test/ut/spec/data/dataValueHelper.test.js
index 8eb7309..cf0c627 100644
--- a/test/ut/spec/data/dataValueHelper.test.js
+++ b/test/ut/spec/data/dataValueHelper.test.js
@@ -20,189 +20,374 @@
const dataValueHelper = require('../../../../lib/data/helper/dataValueHelper');
-describe('data/helper/dataValueHelper', function () {
+const NO_SUCH_CASE = 'NO_SUCH_CASE';
+
+// Tags for relational comparison cases.
+// LT: less than, GT: greater than, INCMPR: incomparable
+const TAG = {
+ ONE_OR_TWO_NUMBER_L_LT_R: 'ONE_OR_TWO_NUMBER_L_LT_R',
+ ONE_OR_TWO_NUMBER_L_GT_R: 'ONE_OR_TWO_NUMBER_L_GT_R',
+ TWO_STRING_L_LT_R: 'TWO_STRING_L_LT_R',
+ TWO_STRING_L_GT_R: 'TWO_STRING_L_GT_R',
+ TWO_STRING_ONLY_NUMERIC_EQ: 'TWO_STRING_ONLY_NUMERIC_EQ',
+ STRICT_EQ: 'STRICT_EQ',
+ ONE_NUMBER_NUMERIC_EQ: 'ONE_NUMBER_NUMERIC_EQ',
+ BOTH_INCMPR_NOT_EQ: 'BOTH_INCMPR_NOT_EQ',
+ ONLY_L_INCMPR: 'ONLY_L_INCMPR',
+ ONLY_R_INCMPR: 'ONLY_R_INCMPR'
+};
+const tagRevertPairs = [
+ ['ONE_OR_TWO_NUMBER_L_LT_R', 'ONE_OR_TWO_NUMBER_L_GT_R'],
+ ['TWO_STRING_L_LT_R', 'TWO_STRING_L_GT_R'],
+ ['TWO_STRING_ONLY_NUMERIC_EQ', 'TWO_STRING_ONLY_NUMERIC_EQ'],
+ ['STRICT_EQ', 'STRICT_EQ'],
+ ['ONE_NUMBER_NUMERIC_EQ', 'ONE_NUMBER_NUMERIC_EQ'],
+ ['BOTH_INCMPR_NOT_EQ', 'BOTH_INCMPR_NOT_EQ'],
+ ['ONLY_L_INCMPR', 'ONLY_R_INCMPR']
+];
+
+const filterResultMap = {
+ ONE_OR_TWO_NUMBER_L_LT_R: {
+ lt: true,
+ lte: true,
+ gt: false,
+ gte: false,
+ eq: false,
+ ne: true
+ },
+ ONE_OR_TWO_NUMBER_L_GT_R: {
+ lt: false,
+ lte: false,
+ gt: true,
+ gte: true,
+ eq: false,
+ ne: true
+ },
+ TWO_STRING_L_LT_R: {
+ lt: NO_SUCH_CASE,
+ lte: NO_SUCH_CASE,
+ gt: NO_SUCH_CASE,
+ gte: NO_SUCH_CASE,
+ eq: false,
+ ne: true
+ },
+ TWO_STRING_L_GT_R: {
+ lt: NO_SUCH_CASE,
+ lte: NO_SUCH_CASE,
+ gt: NO_SUCH_CASE,
+ gte: NO_SUCH_CASE,
+ eq: false,
+ ne: true
+ },
+ TWO_STRING_ONLY_NUMERIC_EQ: {
+ lt: NO_SUCH_CASE,
+ lte: NO_SUCH_CASE,
+ gt: NO_SUCH_CASE,
+ gte: NO_SUCH_CASE,
+ eq: false,
+ ne: true
+ },
+ STRICT_EQ: {
+ lt: false,
+ lte: true,
+ gt: false,
+ gte: true,
+ eq: true,
+ ne: false
+ },
+ ONE_NUMBER_NUMERIC_EQ: {
+ lt: false,
+ lte: true,
+ gt: false,
+ gte: true,
+ eq: true,
+ ne: false
+ },
+ BOTH_INCMPR_NOT_EQ: {
+ lt: false,
+ lte: false,
+ gt: false,
+ gte: false,
+ eq: false,
+ ne: true
+ },
+ ONLY_L_INCMPR: {
+ lt: false,
+ lte: false,
+ gt: false,
+ gte: false,
+ eq: false,
+ ne: true
+ },
+ ONLY_R_INCMPR: {
+ lt: false,
+ lte: false,
+ gt: false,
+ gte: false,
+ eq: false,
+ ne: true
+ }
+};
+
+const sortResultMap = {
+ ONE_OR_TWO_NUMBER_L_LT_R: {
+ asc_incmprmin: -1,
+ asc_incmprmax: -1,
+ desc_incmprmin: 1,
+ desc_incmprmax: 1
+ },
+ ONE_OR_TWO_NUMBER_L_GT_R: {
+ asc_incmprmin: 1,
+ asc_incmprmax: 1,
+ desc_incmprmin: -1,
+ desc_incmprmax: -1
+ },
+ TWO_STRING_L_LT_R: {
+ asc_incmprmin: -1,
+ asc_incmprmax: -1,
+ desc_incmprmin: 1,
+ desc_incmprmax: 1
+ },
+ TWO_STRING_L_GT_R: {
+ asc_incmprmin: 1,
+ asc_incmprmax: 1,
+ desc_incmprmin: -1,
+ desc_incmprmax: -1
+ },
+ TWO_STRING_ONLY_NUMERIC_EQ: {
+ asc_incmprmin: 0,
+ asc_incmprmax: 0,
+ desc_incmprmin: 0,
+ desc_incmprmax: 0
+ },
+ STRICT_EQ: {
+ asc_incmprmin: 0,
+ asc_incmprmax: 0,
+ desc_incmprmin: 0,
+ desc_incmprmax: 0
+ },
+ ONE_NUMBER_NUMERIC_EQ: {
+ asc_incmprmin: 0,
+ asc_incmprmax: 0,
+ desc_incmprmin: 0,
+ desc_incmprmax: 0
+ },
+ BOTH_INCMPR_NOT_EQ: {
+ asc_incmprmin: 0,
+ asc_incmprmax: 0,
+ desc_incmprmin: 0,
+ desc_incmprmax: 0
+ },
+ ONLY_L_INCMPR: {
+ asc_incmprmin: -1,
+ asc_incmprmax: 1,
+ desc_incmprmin: 1,
+ desc_incmprmax: -1
+ },
+ ONLY_R_INCMPR: {
+ asc_incmprmin: 1,
+ asc_incmprmax: -1,
+ desc_incmprmin: -1,
+ desc_incmprmax: 1
+ }
+};
+
+/**
+ * @param {(lval: unknown, rval: unknown, caseTag: TAG) => void} evalFn
+ */
+function eachRelationalComparisonCase(evalFn) {
+
+ const FULL_WIDTH_SPACE = String.fromCharCode(12288);
+
+ const testerMap = {
+ notEqualAndHasOrder: function () {
+ expectDual(123, 555, TAG.ONE_OR_TWO_NUMBER_L_LT_R);
+ expectDual(-123, -555, TAG.ONE_OR_TWO_NUMBER_L_GT_R);
+ expectDual(-123, 123, TAG.ONE_OR_TWO_NUMBER_L_LT_R);
- describe('relational_comparison', function () {
+ expectDual(Infinity, 123, TAG.ONE_OR_TWO_NUMBER_L_GT_R);
+ expectDual(-Infinity, -123, TAG.ONE_OR_TWO_NUMBER_L_LT_R);
+ expectDual('Infinity', 123, TAG.ONE_OR_TWO_NUMBER_L_GT_R);
+ expectDual('-Infinity', 123, TAG.ONE_OR_TWO_NUMBER_L_LT_R);
+ expectDual(123, '555', TAG.ONE_OR_TWO_NUMBER_L_LT_R);
+ expectDual(555, '555.6', TAG.ONE_OR_TWO_NUMBER_L_LT_R);
+ expectDual('-555', -555.6, TAG.ONE_OR_TWO_NUMBER_L_GT_R);
+ expectDual(123, ' 555 ', TAG.ONE_OR_TWO_NUMBER_L_LT_R);
+ expectDual(' -555 ', 123, TAG.ONE_OR_TWO_NUMBER_L_LT_R);
+ expectDual(123, ' \r \n 555 \t ' + FULL_WIDTH_SPACE, TAG.ONE_OR_TWO_NUMBER_L_LT_R);
+ },
- function expectDual(evalFn, lval, rval, resultLR, resultRL) {
- expect(evalFn(lval, rval)).toEqual(resultLR);
- expect(evalFn(rval, lval)).toEqual(resultRL);
+ notEqualAndNoOrder: function () {
+ const makeDate = () => new Date(2012, 5, 12);
+ const makeFn = () => function () {};
+
+ expectDual(NaN, NaN, TAG.BOTH_INCMPR_NOT_EQ);
+ expectDual(NaN, -NaN, TAG.BOTH_INCMPR_NOT_EQ);
+ expectDual(NaN, 0, TAG.ONLY_L_INCMPR);
+ expectDual(NaN, 2, TAG.ONLY_L_INCMPR);
+ expectDual('NaN', NaN, TAG.BOTH_INCMPR_NOT_EQ);
+ expectDual('NaN', 0, TAG.ONLY_L_INCMPR);
+ expectDual('NaN', 2, TAG.ONLY_L_INCMPR);
+ expectDual('-NaN', -NaN, TAG.BOTH_INCMPR_NOT_EQ);
+ expectDual('-NaN', 0, TAG.ONLY_L_INCMPR);
+ expectDual('-NaN', 2, TAG.ONLY_L_INCMPR);
+ expectDual(true, 0, TAG.ONLY_L_INCMPR);
+ expectDual(false, 1, TAG.ONLY_L_INCMPR);
+ expectDual('true', 0, TAG.ONLY_L_INCMPR);
+ expectDual('false', 1, TAG.ONLY_L_INCMPR);
+ expectDual(undefined, 2, TAG.ONLY_L_INCMPR);
+ expectDual(undefined, 0, TAG.ONLY_L_INCMPR);
+ expectDual(null, 2, TAG.ONLY_L_INCMPR);
+ expectDual(null, 0, TAG.ONLY_L_INCMPR);
+ expectDual(makeDate(), 0, TAG.ONLY_L_INCMPR);
+ expectDual(makeDate(), makeDate(), TAG.BOTH_INCMPR_NOT_EQ);
+ expectDual(makeDate(), +makeDate(), TAG.ONLY_L_INCMPR);
+ expectDual([], 1, TAG.ONLY_L_INCMPR);
+ expectDual([], 0, TAG.ONLY_L_INCMPR);
+ expectDual({}, 1, TAG.ONLY_L_INCMPR);
+ expectDual([], '0', TAG.ONLY_L_INCMPR);
+ expectDual({}, '1', TAG.ONLY_L_INCMPR);
+ expectDual({}, 0, TAG.ONLY_L_INCMPR);
+ expectDual({}, '1', TAG.ONLY_L_INCMPR);
+ expectDual({}, '0', TAG.ONLY_L_INCMPR);
+ expectDual(/1/, 0, TAG.ONLY_L_INCMPR);
+ expectDual(/0/, 0, TAG.ONLY_L_INCMPR);
+ expectDual('555a', 123, TAG.ONLY_L_INCMPR);
+ expectDual('abc', 123, TAG.ONLY_L_INCMPR);
+ expectDual('abc', '123', TAG.ONLY_L_INCMPR);
+ expectDual('abc', 'abcde', TAG.TWO_STRING_L_LT_R);
+ expectDual('abc', 'abc', TAG.STRICT_EQ);
+ expectDual('2', '12', TAG.TWO_STRING_L_LT_R); // '2' > '12' in JS but should not happen here.
+ expectDual(' ', '', TAG.TWO_STRING_L_GT_R);
+ expectDual(0.5, '0. 5', TAG.ONLY_R_INCMPR);
+ expectDual('0.5', '0. 5', TAG.ONLY_R_INCMPR);
+ expectDual('- 5', -5, TAG.ONLY_L_INCMPR);
+ expectDual('-123.5', ' -123.5 ', TAG.TWO_STRING_ONLY_NUMERIC_EQ);
+ expectDual('0x11', 17, TAG.ONLY_L_INCMPR); // not 17 in int16.
+ expectDual('0x11', 0, TAG.ONLY_L_INCMPR);
+ expectDual('0x0', 0, TAG.ONLY_L_INCMPR);
+ expectDual('0. 5', 0.5, TAG.ONLY_L_INCMPR);
+ expectDual('0 .5', 0.5, TAG.ONLY_L_INCMPR);
+ expectDual('', 2, TAG.ONLY_L_INCMPR);
+ expectDual('', 0, TAG.ONLY_L_INCMPR);
+ expectDual(' ', 2, TAG.ONLY_L_INCMPR);
+ expectDual(' ', 0, TAG.ONLY_L_INCMPR);
+ expectDual(' \n', '\n', TAG.TWO_STRING_L_GT_R);
+ expectDual('\n', 0, TAG.ONLY_L_INCMPR);
+ expectDual('\n', 2, TAG.ONLY_L_INCMPR);
+ expectDual({}, {}, TAG.BOTH_INCMPR_NOT_EQ);
+ expectDual({}, [], TAG.BOTH_INCMPR_NOT_EQ);
+ expectDual(makeFn(), makeFn(), TAG.BOTH_INCMPR_NOT_EQ);
+ expectDual(makeFn(), 0, TAG.ONLY_L_INCMPR);
+ expectDual(makeFn(), 1, TAG.ONLY_L_INCMPR);
+ expectDual(makeFn(), makeFn().toString(), TAG.BOTH_INCMPR_NOT_EQ);
+ },
+
+ equalNumeric: function () {
+ expectDual(123, 123, TAG.STRICT_EQ);
+ expectDual(1e3, 1000, TAG.STRICT_EQ);
+ expectDual(-1e3, -1000, TAG.STRICT_EQ);
+ expectDual('1e3', 1000, TAG.ONE_NUMBER_NUMERIC_EQ);
+ expectDual('-1e3', -1000, TAG.ONE_NUMBER_NUMERIC_EQ);
+ expectDual(123, '123', TAG.ONE_NUMBER_NUMERIC_EQ);
+ expectDual(123, ' 123 ', TAG.ONE_NUMBER_NUMERIC_EQ);
+ expectDual(123.5, ' \n \r 123.5 \t ', TAG.ONE_NUMBER_NUMERIC_EQ);
+ expectDual(123.5, 123.5 + FULL_WIDTH_SPACE, TAG.ONE_NUMBER_NUMERIC_EQ);
+ expectDual(' -123.5 ', -123.5, TAG.ONE_NUMBER_NUMERIC_EQ);
+ expectDual('011', 11, TAG.ONE_NUMBER_NUMERIC_EQ); // not 9 in int8.
+ },
+
+ equalOtherTypes: function () {
+ const emptyObj = {};
+ const emptyArr = [];
+ const date = new Date(2012, 5, 12);
+ const fn = function () {};
+ expectDual(emptyObj, emptyObj, TAG.STRICT_EQ);
+ expectDual(emptyArr, emptyArr, TAG.STRICT_EQ);
+ expectDual(date, date, TAG.STRICT_EQ);
+ expectDual(fn, fn, TAG.STRICT_EQ);
}
+ };
+
+ function expectDual(lval, rval, caseTag) {
+ validateCaseTag(caseTag);
+ evalFn(lval, rval, caseTag);
+
+ const revertedCaseTag = findRevertTag(caseTag);
+ validateCaseTag(revertedCaseTag);
+ evalFn(rval, lval, revertedCaseTag);
+ }
+
+ function validateCaseTag(caseTag) {
+ expect(TAG.hasOwnProperty(caseTag)).toEqual(true);
+ }
- const testerMap = {
-
- notEqualAndHasOrder: function (evalFn, op) {
- let asc;
- let desc;
- if (op === 'lt' || op === 'lte') {
- asc = true;
- desc = false;
- }
- else if (op === 'gt' || op === 'gte') {
- asc = false;
- desc = true;
- }
- else if (op === 'eq') {
- asc = desc = false;
- }
- else if (op === 'ne') {
- asc = desc = true;
- }
-
- expectDual(evalFn, 123, 555, asc, desc);
- expectDual(evalFn, -123, -555, desc, asc);
- expectDual(evalFn, -123, 123, asc, desc);
-
- expectDual(evalFn, Infinity, 123, desc, asc);
- expectDual(evalFn, -Infinity, -123, asc, desc);
- expectDual(evalFn, 'Infinity', 123, desc, asc);
- expectDual(evalFn, '-Infinity', 123, asc, desc);
- expectDual(evalFn, 123, '555', asc, desc);
- expectDual(evalFn, 555, '555.6', asc, desc);
- expectDual(evalFn, '-555', -555.6, desc, asc);
- expectDual(evalFn, 123, ' 555 ', asc, desc);
- expectDual(evalFn, ' -555 ', 123, asc, desc);
- expectDual(evalFn, 123, ' \r \n 555 \t ' + String.fromCharCode(12288), asc, desc);
- },
-
- notEqualAndNoOrder: function (evalFn, op) {
- const result = op === 'ne';
- const makeDate = () => new Date(2012, 5, 12);
- const makeFn = () => function () {};
-
- expectDual(evalFn, NaN, NaN, result, result);
- expectDual(evalFn, NaN, -NaN, result, result);
- expectDual(evalFn, NaN, 0, result, result);
- expectDual(evalFn, NaN, 2, result, result);
- expectDual(evalFn, 'NaN', NaN, result, result);
- expectDual(evalFn, 'NaN', 0, result, result);
- expectDual(evalFn, 'NaN', 2, result, result);
- expectDual(evalFn, '-NaN', -NaN, result, result);
- expectDual(evalFn, '-NaN', 0, result, result);
- expectDual(evalFn, '-NaN', 2, result, result);
- expectDual(evalFn, true, 0, result, result);
- expectDual(evalFn, false, 1, result, result);
- expectDual(evalFn, 'true', 0, result, result);
- expectDual(evalFn, 'false', 1, result, result);
- expectDual(evalFn, undefined, 2, result, result);
- expectDual(evalFn, undefined, 0, result, result);
- expectDual(evalFn, null, 2, result, result);
- expectDual(evalFn, null, 0, result, result);
- expectDual(evalFn, makeDate(), 0, result, result);
- expectDual(evalFn, makeDate(), makeDate(), result, result);
- expectDual(evalFn, makeDate(), +makeDate(), result, result);
- expectDual(evalFn, [], 1, result, result);
- expectDual(evalFn, [], 0, result, result);
- expectDual(evalFn, {}, 1, result, result);
- expectDual(evalFn, [], '0', result, result);
- expectDual(evalFn, {}, '1', result, result);
- expectDual(evalFn, {}, 0, result, result);
- expectDual(evalFn, {}, '1', result, result);
- expectDual(evalFn, {}, '0', result, result);
- expectDual(evalFn, /1/, 0, result, result);
- expectDual(evalFn, /0/, 0, result, result);
- expectDual(evalFn, '555a', 123, result, result);
- expectDual(evalFn, '2', '12', result, result); // '2' > '12' in JS but should not happen here.
- expectDual(evalFn, ' ', '', result, result);
- expectDual(evalFn, 0.5, '0. 5', result, result);
- expectDual(evalFn, '0.5', '0. 5', result, result);
- expectDual(evalFn, '- 5', -5, result, result);
- expectDual(evalFn, '-123.5', ' -123.5 ', result, result);
- expectDual(evalFn, '0x11', 17, result, result); // not 17 in int16.
- expectDual(evalFn, '0x11', 0, result, result);
- expectDual(evalFn, '0x0', 0, result, result);
- expectDual(evalFn, '0. 5', 0.5, result, result);
- expectDual(evalFn, '0 .5', 0.5, result, result);
- expectDual(evalFn, '', 2, result, result);
- expectDual(evalFn, '', 0, result, result);
- expectDual(evalFn, ' ', 2, result, result);
- expectDual(evalFn, ' ', 0, result, result);
- expectDual(evalFn, ' \n', '\n', result, result);
- expectDual(evalFn, '\n', 0, result, result);
- expectDual(evalFn, '\n', 2, result, result);
- expectDual(evalFn, {}, {}, result, result);
- expectDual(evalFn, {}, [], result, result);
- expectDual(evalFn, makeFn(), makeFn(), result, result);
- expectDual(evalFn, makeFn(), 0, result, result);
- expectDual(evalFn, makeFn(), 1, result, result);
- expectDual(evalFn, makeFn(), makeFn().toString(), result, result);
- },
-
- numericEqual: function (evalFn, op) {
- const result = op === 'eq' || op === 'lte' || op === 'gte';
-
- expectDual(evalFn, 123, 123, result, result);
- expectDual(evalFn, 1e3, 1000, result, result);
- expectDual(evalFn, -1e3, -1000, result, result);
- expectDual(evalFn, '1e3', 1000, result, result);
- expectDual(evalFn, '-1e3', -1000, result, result);
- expectDual(evalFn, 123, '123', result, result);
- expectDual(evalFn, 123, ' 123 ', result, result);
- expectDual(evalFn, 123.5, ' \n \r 123.5 \t ', result, result);
- expectDual(evalFn, 123.5, 123.5 + String.fromCharCode(12288), result, result);
- expectDual(evalFn, ' -123.5 ', -123.5, result, result);
- expectDual(evalFn, '011', 11, result, result); // not 9 in int8.
- },
-
- otherTypesEqual: function (evalFn, op) {
- const result = op === 'eq';
-
- const emptyObj = {};
- const emptyArr = [];
- const date = new Date(2012, 5, 12);
- const fn = function () {};
- expectDual(evalFn, emptyObj, emptyObj, result, result);
- expectDual(evalFn, emptyArr, emptyArr, result, result);
- expectDual(evalFn, date, date, result, result);
- expectDual(evalFn, fn, fn, result, result);
+ function findRevertTag(caseTag) {
+ for (let i = 0; i < tagRevertPairs.length; i++) {
+ const item = tagRevertPairs[i];
+ if (item[0] === caseTag) {
+ return item[1];
}
- };
-
- function doTest(op) {
- expect(['lt', 'lte', 'gt', 'gte', 'eq', 'ne'].indexOf(op) >= 0).toEqual(true);
- it(op, () => {
- const comparator0 = dataValueHelper.createRelationalComparator(op);
- Object.keys(testerMap).forEach(name => {
- const evalFn = (lVal, rVal) => {
- return comparator0.evaluate(lVal, rVal);
- };
- testerMap[name](evalFn, op);
- });
+ else if (item[1] === caseTag) {
+ return item[0];
+ }
+ }
+ }
+
+ Object.keys(testerMap).forEach(name => testerMap[name]());
+}
+
+
+describe('data/helper/dataValueHelper', function () {
- Object.keys(testerMap).forEach(name => {
- const evalFn = (lVal, rVal) => {
- const comparator1 = dataValueHelper.createRelationalComparator(op, true, rVal);
- return comparator1.evaluate(lVal);
- };
- testerMap[name](evalFn, op);
+ describe('filter_relational_comparison', function () {
+ function testFilterComparator(op) {
+ it(op + '_filter_comparator', () => {
+ eachRelationalComparisonCase((lval, rval, caseTag) => {
+ expect(filterResultMap.hasOwnProperty[caseTag]);
+ expect(filterResultMap[caseTag].hasOwnProperty[op]);
+ const expectedResult = filterResultMap[caseTag][op];
+
+ if ((op === 'lt' || op === 'lte' || op === 'gt' || op === 'gte')
+ && typeof rval !== 'number'
+ ) {
+ expect(() => {
+ dataValueHelper.createFilterComparator(op, rval);
+ }).toThrow();
+ }
+ else {
+ const comparator = dataValueHelper.createFilterComparator(op, rval);
+ expect(comparator.evaluate(lval, rval)).toEqual(expectedResult);
+ }
});
});
}
+ testFilterComparator('lt');
+ testFilterComparator('lte');
+ testFilterComparator('gt');
+ testFilterComparator('gte');
+ testFilterComparator('eq');
+ testFilterComparator('ne');
+ });
- doTest('lt');
- doTest('lte');
- doTest('gt');
- doTest('gte');
- doTest('eq');
- doTest('ne');
-
- it('isRelationalOperator', function () {
- expect(dataValueHelper.isRelationalOperator('lt')).toEqual(true);
- expect(dataValueHelper.isRelationalOperator('lte')).toEqual(true);
- expect(dataValueHelper.isRelationalOperator('gt')).toEqual(true);
- expect(dataValueHelper.isRelationalOperator('gte')).toEqual(true);
- expect(dataValueHelper.isRelationalOperator('eq')).toEqual(true);
- expect(dataValueHelper.isRelationalOperator('ne')).toEqual(true);
- expect(dataValueHelper.isRelationalOperator('')).toEqual(false);
-
- expect(dataValueHelper.isRelationalOperator(null)).toEqual(false);
- expect(dataValueHelper.isRelationalOperator(undefined)).toEqual(false);
- expect(dataValueHelper.isRelationalOperator(NaN)).toEqual(false);
- expect(dataValueHelper.isRelationalOperator('neq')).toEqual(false);
- expect(dataValueHelper.isRelationalOperator('ge')).toEqual(false);
- expect(dataValueHelper.isRelationalOperator('le')).toEqual(false);
- });
-
+ describe('sort_relational_comparison', function () {
+ function testSortComparator(order, incomparable) {
+ const key = order + '_incmpr' + incomparable;
+ const SortOrderComparator = dataValueHelper.SortOrderComparator;
+ const sortOrderComparator = new SortOrderComparator(order, incomparable);
+ it(key + '_sort_comparator', () => {
+ eachRelationalComparisonCase((lval, rval, caseTag) => {
+ expect(sortResultMap.hasOwnProperty[caseTag]);
+ expect(sortResultMap[caseTag].hasOwnProperty[key]);
+ const expectedResult = sortResultMap[caseTag][key];
+ expect(sortOrderComparator.evaluate(lval, rval)).toEqual(expectedResult);
+ });
+ });
+ }
+ testSortComparator('asc', 'min');
+ testSortComparator('asc', 'max');
+ testSortComparator('desc', 'min');
+ testSortComparator('desc', 'max');
});
});
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org