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 <sushuang0...@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-unsubscr...@echarts.apache.org For additional commands, e-mail: commits-h...@echarts.apache.org