提交 81d1306b 编写于 作者: 1 100pah

fix: [data-transform] (1) clarity the detail of value comparison. (2) rename "parse" to "parser".

上级 ff906a44
......@@ -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;
......
......@@ -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;
}
......@@ -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__) {
......
......@@ -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));
}
......@@ -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,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册