diff --git a/src/component/transform/filterTransform.ts b/src/component/transform/filterTransform.ts index d674f5404dc23b6b1e7a61b34fc2a53cf5aacd8d..627f3724c010506985742b3053749aba11b53fa3 100644 --- a/src/component/transform/filterTransform.ts +++ b/src/component/transform/filterTransform.ts @@ -17,8 +17,10 @@ * under the License. */ -import { DataTransformOption, ExternalDataTransform } from '../../data/helper/transform'; -import { DimensionIndex, OptionDataItem } from '../../util/types'; +import { + DataTransformOption, ExternalDataTransform, DataTransformDataItem, ExternalDataTransformResultItem +} from '../../data/helper/transform'; +import { DimensionIndex } from '../../util/types'; import { parseConditionalExpression, ConditionalExpressionOption } from '../../util/conditionalExpression'; import { hasOwn, createHashMap } from 'zrender/src/core/util'; import { makePrintable, throwError } from '../../util/log'; @@ -41,7 +43,7 @@ export const filterTransform: ExternalDataTransform = { // is better than return the entire raw soruce for user to find the mistake. const upstream = params.upstream; - let rawItem: OptionDataItem; + let rawItem: DataTransformDataItem; const condition = parseConditionalExpression<{ dimIdx: DimensionIndex }>(params.config, { @@ -84,12 +86,12 @@ export const filterTransform: ExternalDataTransform = { for (let i = 0, len = upstream.count(); i < len; i++) { rawItem = upstream.getRawDataItem(i); if (condition.evaluate()) { - resultData.push(rawItem); + resultData.push(rawItem as any); } } return { - data: resultData + data: resultData as ExternalDataTransformResultItem['data'] }; } }; diff --git a/src/component/transform/sortTransform.ts b/src/component/transform/sortTransform.ts index a97382b1c1b96c587baa0af55737fb4557cee2a1..dbe246c6933ef16c3d4c9869ad06b01c3000af35 100644 --- a/src/component/transform/sortTransform.ts +++ b/src/component/transform/sortTransform.ts @@ -17,7 +17,9 @@ * under the License. */ -import { DataTransformOption, ExternalDataTransform } from '../../data/helper/transform'; +import { + DataTransformOption, ExternalDataTransform, ExternalDataTransformResultItem +} from '../../data/helper/transform'; import { DimensionLoose, DimensionIndex, OptionDataValue, SOURCE_FORMAT_ARRAY_ROWS, SOURCE_FORMAT_OBJECT_ROWS } from '../../util/types'; @@ -199,7 +201,7 @@ export const sortTransform: ExternalDataTransform = { }); return { - data: resultData + data: resultData as ExternalDataTransformResultItem['data'] }; } }; diff --git a/src/data/Source.ts b/src/data/Source.ts index a17e871690242af656b5b2e11aeb4be5ffe3cb18..f5516e666929ade3d9585adf42621b95da8f7259 100644 --- a/src/data/Source.ts +++ b/src/data/Source.ts @@ -287,7 +287,10 @@ function makeEncodeDefine( : null; } -function detectSourceFormat(data: DatasetOption['source']): SourceFormat { +/** + * Note: An empty array will be detected as `SOURCE_FORMAT_ARRAY_ROWS`. + */ +export function detectSourceFormat(data: DatasetOption['source']): SourceFormat { let sourceFormat: SourceFormat = SOURCE_FORMAT_UNKNOWN; if (isTypedArray(data)) { @@ -323,9 +326,6 @@ function detectSourceFormat(data: DatasetOption['source']): SourceFormat { } } } - else if (data != null) { - throw new Error('Invalid data'); - } return sourceFormat; } diff --git a/src/data/helper/sourceHelper.ts b/src/data/helper/sourceHelper.ts index 2406d0f38d47a525732b3398cafef3c249ebd786..0d17f5c589acca1a4e94968e386672d7b01d1975 100644 --- a/src/data/helper/sourceHelper.ts +++ b/src/data/helper/sourceHelper.ts @@ -82,31 +82,6 @@ export function resetSourceDefaulter(ecModel: GlobalModel): void { innerGlobalModel(ecModel).datasetMap = createHashMap(); } -// See [DIMENSION_INHERIT_RULE] in `sourceManager.ts`. -export function inheritSourceMetaRawOption( - upstream: Source, // Can be null/undefined - newMetaRawOption: SourceMetaRawOption // Can NOT be null/undefined -): SourceMetaRawOption { - const parentMetaRawOption = upstream ? upstream.metaRawOption : null; - const seriesLayoutBy = retrieve2( - newMetaRawOption.seriesLayoutBy, - parentMetaRawOption ? parentMetaRawOption.seriesLayoutBy : null - ); - // sourceHeader and dimensions should use the "detected result" rather than "meta raw". - // Consider the case: transform return only "data" but no "dimensions", that should means inherit - // dimensions definition from upstream. But the returned data does not contain header line and can not - // be used as dimension-detection. In this case we should use "detected dimensions" of upstream directly. - const sourceHeader = retrieve2( - newMetaRawOption.sourceHeader, - upstream ? upstream.startIndex : null - ); - const dimensions = retrieve2( - newMetaRawOption.dimensions, - upstream ? upstream.dimensionsDefine : null - ); - return { seriesLayoutBy, sourceHeader, dimensions }; -} - /** * [The strategy of the arrengment of data dimensions for dataset]: * "value way": all axes are non-category axes. So series one by one take diff --git a/src/data/helper/sourceManager.ts b/src/data/helper/sourceManager.ts index 1e88f7b4d1e4c5adec2fa3a56cc4347e58869872..9b3dba42e6c5c3c401f7728136ccde796f1a12b8 100644 --- a/src/data/helper/sourceManager.ts +++ b/src/data/helper/sourceManager.ts @@ -19,7 +19,7 @@ import { DatasetModel } from '../../component/dataset'; import SeriesModel from '../../model/Series'; -import { setAsPrimitive, map, isTypedArray, assert, each } from 'zrender/src/core/util'; +import { setAsPrimitive, map, isTypedArray, assert, each, retrieve2 } from 'zrender/src/core/util'; import { SourceMetaRawOption, Source, createSource, cloneSourceShallow } from '../Source'; import { SeriesEncodableModel, OptionSourceData, @@ -27,8 +27,7 @@ import { SourceFormat, SeriesLayoutBy, OptionSourceHeader, DimensionDefinitionLoose } from '../../util/types'; import { - querySeriesUpstreamDatasetModel, queryDatasetUpstreamDatasetModels, - inheritSourceMetaRawOption + querySeriesUpstreamDatasetModel, queryDatasetUpstreamDatasetModels } from './sourceHelper'; import { applyDataTransform } from './transform'; @@ -192,7 +191,7 @@ export class SourceManager { const seriesModel = sourceHost as SeriesEncodableModel; let data; let sourceFormat: SourceFormat; - let upSource; + let upSource: Source; // Has upstream dataset if (hasUpstream) { @@ -212,14 +211,27 @@ export class SourceManager { } // See [REQUIREMENT_MEMO], merge settings on series and parent dataset if it is root. - const thisMetaRawOption = inheritSourceMetaRawOption( - upSource, - this._getSourceMetaRawOption() + const newMetaRawOption = this._getSourceMetaRawOption(); + const upMetaRawOption = upSource ? upSource.metaRawOption : null; + const seriesLayoutBy = retrieve2( + newMetaRawOption.seriesLayoutBy, + upMetaRawOption ? upMetaRawOption.seriesLayoutBy : null + ); + const sourceHeader = retrieve2( + newMetaRawOption.sourceHeader, + upMetaRawOption ? upMetaRawOption.sourceHeader : null + ); + // Note here we should not use `upSource.dimensionsDefine`. Consider the case: + // `upSource.dimensionsDefine` is detected by `seriesLayoutBy: 'column'`, + // but series need `seriesLayoutBy: 'row'`. + const dimensions = retrieve2( + newMetaRawOption.dimensions, + upMetaRawOption ? upMetaRawOption.dimensions : null ); resultSourceList = [createSource( data, - thisMetaRawOption, + { seriesLayoutBy, sourceHeader, dimensions }, sourceFormat, seriesModel.get('encode', true) )]; diff --git a/src/data/helper/transform.ts b/src/data/helper/transform.ts index 349e9857d8f4c3daa310e98d43e3cc14a0c7e1ad..43625cd917c66de1dc6bc8bc0a071c9dae44d5e6 100644 --- a/src/data/helper/transform.ts +++ b/src/data/helper/transform.ts @@ -18,26 +18,22 @@ */ import { - Dictionary, OptionSourceData, DimensionDefinitionLoose, - SourceFormat, DimensionDefinition, OptionDataItem, DimensionIndex, + Dictionary, DimensionDefinitionLoose, + SourceFormat, DimensionDefinition, DimensionIndex, OptionDataValue, DimensionLoose, DimensionName, ParsedValue, SERIES_LAYOUT_BY_COLUMN, SOURCE_FORMAT_OBJECT_ROWS, SOURCE_FORMAT_ARRAY_ROWS, OptionSourceDataObjectRows, OptionSourceDataArrayRows } from '../../util/types'; import { normalizeToArray } from '../../util/model'; import { - createHashMap, bind, each, hasOwn, map, clone, isObject, - isArrayLike, - extend, - isArray + createHashMap, bind, each, hasOwn, map, clone, isObject, extend } from 'zrender/src/core/util'; import { getRawSourceItemGetter, getRawSourceDataCounter, getRawSourceValueGetter } from './dataProvider'; import { parseDataValue } from './dataValueHelper'; -import { inheritSourceMetaRawOption } from './sourceHelper'; import { consoleLog, makePrintable, throwError } from '../../util/log'; -import { createSource, Source } from '../Source'; +import { createSource, Source, SourceMetaRawOption, detectSourceFormat } from '../Source'; export type PipedDataTransformOption = DataTransformOption[]; @@ -71,7 +67,7 @@ export interface ExternalDataTransformResultItem { /** * If `data` is null/undefined, inherit upstream data. */ - data: OptionSourceData; + data: OptionSourceDataArrayRows | OptionSourceDataObjectRows; /** * A `transform` can optionally return a dimensions definition. * The rule: @@ -83,6 +79,7 @@ export interface ExternalDataTransformResultItem { */ dimensions?: DimensionDefinitionLoose[]; } +export type DataTransformDataItem = ExternalDataTransformResultItem['data'][number]; export interface ExternalDimensionDefinition extends Partial { // Mandatory index: DimensionIndex; @@ -110,7 +107,7 @@ export class ExternalSource { throw new Error('not supported'); } - getRawDataItem(dataIndex: number): OptionDataItem { + getRawDataItem(dataIndex: number): DataTransformDataItem { // Only built-in transform available. throw new Error('not supported'); } @@ -152,7 +149,7 @@ export class ExternalSource { return; } - retrieveValueFromItem(dataItem: OptionDataItem, dimIndex: DimensionIndex): OptionDataValue { + retrieveValueFromItem(dataItem: DataTransformDataItem, dimIndex: DimensionIndex): OptionDataValue { return; } @@ -169,6 +166,17 @@ function createExternalSource(internalSource: Source, externalTransform: Externa const sourceFormat = extSource.sourceFormat = internalSource.sourceFormat; const sourceHeaderCount = internalSource.startIndex; + let errMsg = ''; + if (internalSource.seriesLayoutBy !== SERIES_LAYOUT_BY_COLUMN) { + // For the logic simplicity in transformer, only 'culumn' is + // supported in data transform. Otherwise, the `dimensionsDefine` + // might be detected by 'row', which probably confuses users. + if (__DEV__) { + errMsg = '`seriesLayoutBy` of upstream dataset can only be "column" in data transform.'; + } + throwError(errMsg); + } + // [MEMO] // Create a new dimensions structure for exposing. // Do not expose all dimension info to users directly. @@ -219,7 +227,7 @@ function createExternalSource(internalSource: Source, externalTransform: Externa const rawItemGetter = getRawSourceItemGetter(sourceFormat, SERIES_LAYOUT_BY_COLUMN); if (externalTransform.__isBuiltIn) { extSource.getRawDataItem = function (dataIndex) { - return rawItemGetter(data, sourceHeaderCount, dimensions, dataIndex); + return rawItemGetter(data, sourceHeaderCount, dimensions, dataIndex) as DataTransformDataItem; }; extSource.getRawData = bind(getRawData, null, internalSource); } @@ -231,7 +239,7 @@ function createExternalSource(internalSource: Source, externalTransform: Externa const rawValueGetter = getRawSourceValueGetter(sourceFormat); extSource.retrieveValue = function (dataIndex, dimIndex) { - const rawItem = rawItemGetter(data, sourceHeaderCount, dimensions, dataIndex); + const rawItem = rawItemGetter(data, sourceHeaderCount, dimensions, dataIndex) as DataTransformDataItem; return retrieveValueFromItem(rawItem, dimIndex); }; const retrieveValueFromItem = extSource.retrieveValueFromItem = function (dataItem, dimIndex) { @@ -253,34 +261,31 @@ function createExternalSource(internalSource: Source, externalTransform: Externa function getRawData(upstream: Source): Source['data'] { const sourceFormat = upstream.sourceFormat; - const data = upstream.data; - if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS - || sourceFormat === SOURCE_FORMAT_OBJECT_ROWS - || !data - || (isArray(data) && !data.length) - ) { - return upstream.data; + if (!isSupportedSourceFormat(sourceFormat)) { + let errMsg = ''; + if (__DEV__) { + errMsg = '`getRawData` is not supported in source format ' + sourceFormat; + } + throwError(errMsg); } - let errMsg = ''; - if (__DEV__) { - errMsg = '`getRawData` is not supported in source format ' + sourceFormat; - } - throwError(errMsg); + return upstream.data; } function cloneRawData(upstream: Source): Source['data'] { const sourceFormat = upstream.sourceFormat; const data = upstream.data; - if (!data) { - return data; - } - else if (isArray(data) && !data.length) { - return []; + if (!isSupportedSourceFormat(sourceFormat)) { + let errMsg = ''; + if (__DEV__) { + errMsg = '`cloneRawData` is not supported in source format ' + sourceFormat; + } + throwError(errMsg); } - else if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) { + + if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) { const result = []; for (let i = 0, len = data.length; i < len; i++) { // Not strictly clone for performance @@ -442,39 +447,91 @@ function applySingleDataTransform( } } - return map(resultList, function (result) { + return map(resultList, function (result, resultIndex) { let errMsg = ''; + if (!isObject(result)) { if (__DEV__) { errMsg = 'A transform should not return some empty results.'; } throwError(errMsg); } - let resultData = result.data; - if (resultData != null) { - if (!isObject(resultData) && !isArrayLike(resultData)) { - if (__DEV__) { - errMsg = 'Result data should be object or array in data transform.'; - } - throwError(errMsg); + + if (!result.data) { + if (__DEV__) { + errMsg = 'Transform result data should be not be null or undefined'; } + throwError(errMsg); } - else { - // Inherit from upstream[0] - resultData = upSourceList[0].data; + + const sourceFormat = detectSourceFormat(result.data); + if (!isSupportedSourceFormat(sourceFormat)) { + if (__DEV__) { + errMsg = 'Transform result data should be array rows or object rows.'; + } + throwError(errMsg); } - const resultMetaRawOption = inheritSourceMetaRawOption( - upSourceList[0], - { + let resultMetaRawOption: SourceMetaRawOption; + const firstUpSource = upSourceList[0]; + + /** + * Intuitively, the end users known the content of the original `dataset.source`, + * calucating the transform result in mind. + * Suppose the original `dataset.source` is: + * ```js + * [ + * ['product', '2012', '2013', '2014', '2015'], + * ['AAA', 41.1, 30.4, 65.1, 53.3], + * ['BBB', 86.5, 92.1, 85.7, 83.1], + * ['CCC', 24.1, 67.2, 79.5, 86.4] + * ] + * ``` + * The dimension info have to be detected from the source data. + * Some of the transformers (like filter, sort) will follow the dimension info + * of upstream, while others use new dimensions (like aggregate). + * Transformer can output a field `dimensions` to define the its own output dimensions. + * We also allow transformers to ignore the output `dimensions` field, and + * inherit the upstream dimensions definition. It can reduce the burden of handling + * dimensions in transformers. + * + * See also [DIMENSION_INHERIT_RULE] in `sourceManager.ts`. + */ + if ( + firstUpSource + && resultIndex === 0 + // If transformer returns `dimensions`, it means that the transformer has different + // dimensions definitions. We do not inherit anything from upstream. + && !result.dimensions + ) { + const startIndex = firstUpSource.startIndex; + // We copy the header of upstream to the result becuase: + // (1) The returned data always does not contain header line and can not be used + // as dimension-detection. In this case we can not use "detected dimensions" of + // upstream directly, because it might be detected based on different `seriesLayoutBy`. + // (2) We should support that the series read the upstream source in `seriesLayoutBy: 'row'`. + // So the original detected header should be add to the result, otherwise they can not be read. + if (startIndex) { + result.data = (firstUpSource.data as []).slice(0, startIndex) + .concat(result.data as []); + } + + resultMetaRawOption = { + seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN, + sourceHeader: startIndex, + dimensions: firstUpSource.metaRawOption.dimensions + }; + } + else { + resultMetaRawOption = { seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN, sourceHeader: 0, dimensions: result.dimensions - } - ); + }; + } return createSource( - resultData, + result.data, resultMetaRawOption, null, null @@ -482,3 +539,6 @@ function applySingleDataTransform( }); } +function isSupportedSourceFormat(sourceFormat: SourceFormat): boolean { + return sourceFormat === SOURCE_FORMAT_ARRAY_ROWS || sourceFormat === SOURCE_FORMAT_OBJECT_ROWS; +} diff --git a/test/legend-feature.html b/test/legend-feature.html new file mode 100644 index 0000000000000000000000000000000000000000..4ce563a2c6ce4cb0958da21028fc32448065924d --- /dev/null +++ b/test/legend-feature.html @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + diff --git a/test/ut/core/utHelper.ts b/test/ut/core/utHelper.ts index bfedb653ae9d80d602a2a7b7d88ebdb9370e4e49..ae23689047703b69eddfa862fb405fd316a5a285 100755 --- a/test/ut/core/utHelper.ts +++ b/test/ut/core/utHelper.ts @@ -27,6 +27,7 @@ import { import { ComponentMainType } from '../../../src/util/types'; import Group from 'zrender/src/graphic/Group'; import Element from 'zrender/src/Element'; +import GlobalModel from '../../../src/model/Global'; export function createChart(params?: { @@ -137,9 +138,13 @@ export function getViewGroup( mainType: ComponentMainType, index?: number ): Group { - const component = chart.getModel().getComponent(mainType, index); + const component = getECModel(chart).getComponent(mainType, index); return component ? chart[ mainType === 'series' ? '_chartsMap' : '_componentsMap' ][component.__viewId].group : null; } +export function getECModel(chart: EChartsType): GlobalModel { + // @ts-ignore + return chart.getModel(); +} diff --git a/test/ut/spec/component/dataZoom/helper.test.ts b/test/ut/spec/component/dataZoom/helper.test.ts index f51da55bc250f5bdb021f9209322603c98e56bab..905ae2c1dbcc5eef9bec5ba30585aea84d6c1dcc 100755 --- a/test/ut/spec/component/dataZoom/helper.test.ts +++ b/test/ut/spec/component/dataZoom/helper.test.ts @@ -19,7 +19,7 @@ */ import { findEffectedDataZooms } from '../../../../../src/component/dataZoom/helper'; -import { createChart } from '../../../core/utHelper'; +import { createChart, getECModel } from '../../../core/utHelper'; import { EChartsType } from '../../../../../src/echarts'; @@ -50,12 +50,12 @@ describe('dataZoom/helper', function () { }); const payload = { type: 'dataZoom', dataZoomIndex: 0 }; - const dzModels = findEffectedDataZooms(chart.getModel(), payload); + const dzModels = findEffectedDataZooms(getECModel(chart), payload); expect(dzModels.length === 3); - expect(dzModels[0] === chart.getModel().getComponent('dataZoom', 0)).toEqual(true); - expect(dzModels[1] === chart.getModel().getComponent('dataZoom', 3)).toEqual(true); - expect(dzModels[2] === chart.getModel().getComponent('dataZoom', 2)).toEqual(true); + expect(dzModels[0] === getECModel(chart).getComponent('dataZoom', 0)).toEqual(true); + expect(dzModels[1] === getECModel(chart).getComponent('dataZoom', 3)).toEqual(true); + expect(dzModels[2] === getECModel(chart).getComponent('dataZoom', 2)).toEqual(true); }); it('findLinkedNodes_crossXY', function () { @@ -72,13 +72,13 @@ describe('dataZoom/helper', function () { }); const payload = { type: 'dataZoom', dataZoomIndex: 0 }; - const dzModels = findEffectedDataZooms(chart.getModel(), payload); + const dzModels = findEffectedDataZooms(getECModel(chart), payload); expect(dzModels.length === 4); - expect(dzModels[0] === chart.getModel().getComponent('dataZoom', 0)).toEqual(true); - expect(dzModels[1] === chart.getModel().getComponent('dataZoom', 1)).toEqual(true); - expect(dzModels[2] === chart.getModel().getComponent('dataZoom', 2)).toEqual(true); - expect(dzModels[3] === chart.getModel().getComponent('dataZoom', 3)).toEqual(true); + expect(dzModels[0] === getECModel(chart).getComponent('dataZoom', 0)).toEqual(true); + expect(dzModels[1] === getECModel(chart).getComponent('dataZoom', 1)).toEqual(true); + expect(dzModels[2] === getECModel(chart).getComponent('dataZoom', 2)).toEqual(true); + expect(dzModels[3] === getECModel(chart).getComponent('dataZoom', 3)).toEqual(true); }); it('findLinkedNodes_emptySourceModel', function () { @@ -95,7 +95,7 @@ describe('dataZoom/helper', function () { }); const payload = { type: 'other' }; - const dzModels = findEffectedDataZooms(chart.getModel(), payload); + const dzModels = findEffectedDataZooms(getECModel(chart), payload); expect(dzModels.length === 0); }); diff --git a/test/ut/spec/component/graphic/setOption.test.ts b/test/ut/spec/component/graphic/setOption.test.ts index 6dddca2994332c75819c64db4f61793d9551c90d..815915dce6ce33a0990499bec072ef4eda8ad291 100755 --- a/test/ut/spec/component/graphic/setOption.test.ts +++ b/test/ut/spec/component/graphic/setOption.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createChart, getGraphicElements } from '../../../core/utHelper'; +import { createChart, getGraphicElements, getECModel } from '../../../core/utHelper'; // import { imageURI } from './setOptionImageURI'; import { EChartsType } from '../../../../../src/echarts'; import Element from 'zrender/src/Element'; @@ -338,8 +338,8 @@ describe('graphic_setOption', function () { ] }); - expect(!!chart.getModel().getComponent('graphic')).toEqual(true); - expect(chart.getModel().getComponent('graphic', 1) == null).toEqual(true); + expect(!!getECModel(chart).getComponent('graphic')).toEqual(true); + expect(getECModel(chart).getComponent('graphic', 1) == null).toEqual(true); }); diff --git a/test/ut/spec/component/visualMap/setOption.test.ts b/test/ut/spec/component/visualMap/setOption.test.ts index 3fe14880a09d57edd43994d93e69321b08dcce31..c0f63df933ffa36dd8b04d1339764341b8ffdc28 100755 --- a/test/ut/spec/component/visualMap/setOption.test.ts +++ b/test/ut/spec/component/visualMap/setOption.test.ts @@ -18,7 +18,7 @@ * under the License. */ -import { createChart } from '../../../core/utHelper'; +import { createChart, getECModel } from '../../../core/utHelper'; import { EChartsType } from '../../../../../src/echarts'; import { EChartsFullOption } from '../../../../../src/option'; import { ContinousVisualMapOption } from '../../../../../src/component/visualMap/ContinuousModel'; @@ -256,7 +256,7 @@ describe('vsiaulMap_setOption', function () { ] }); - const ecModel = chart.getModel(); + const ecModel = getECModel(chart); function getVisual(idx: number, visualType: 'color' | 'opacity' | 'symbol') { return (ecModel.getComponent('visualMap', idx) as VisualMapModel) diff --git a/test/ut/spec/data/dataTransform.test.ts b/test/ut/spec/data/dataTransform.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..21500d1375165c10cc353e462e1648f4307fed6e --- /dev/null +++ b/test/ut/spec/data/dataTransform.test.ts @@ -0,0 +1,185 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsType } from '../../../../src/echarts'; +import { createChart, removeChart, getECModel } from '../../core/utHelper'; +import { EChartsFullOption } from '../../../../src/option'; +import { retrieveRawValue } from '../../../../src/data/helper/dataProvider'; + + +describe('dataTransform', function () { + + let chart: EChartsType; + + beforeEach(function () { + chart = createChart({ + width: 200, + height: 150 + }); + }); + + afterEach(function () { + removeChart(chart); + }); + + function makeDatasetSourceDetection() { + return [ + ['product', '2012', '2013', '2014', '2015'], + ['AAA', 41.1, 30.4, 65.1, 53.3], + ['BBB', 86.5, 92.1, 85.7, 83.1], + ['CCC', 24.1, 67.2, 79.5, 86.4] + ]; + } + + function makeDatasetSourceNonDetectionByRow() { + return { + dimensions: ['2012', '2013', '2014', '2015'], + source: [ + [41.1, 30.4, 65.1, 53.3], + [86.5, 92.1, 85.7, 83.1], + [24.1, 67.2, 79.5, 86.4] + ] + }; + } + + it('forbid_seriesLayoutBy_row', function () { + const option: EChartsFullOption = { + dataset: [{ + source: makeDatasetSourceDetection(), + // This config should cause error thrown. + seriesLayoutBy: 'row' + }, { + transform: { type: 'filter', config: { dimension: 0, ne: '' } } + }], + xAxis: { type: 'category' }, + yAxis: {}, + series: { type: 'bar', datasetIndex: 1 } + }; + + expect(() => { + chart.setOption(option); + }).toThrowError(/column/); + }); + + it('seriesLayoutBy_changed_no_transform', function () { + const option: EChartsFullOption = { + dataset: { + source: makeDatasetSourceDetection() + }, + xAxis: { type: 'category' }, + yAxis: {}, + series: { type: 'bar', seriesLayoutBy: 'row' } + }; + + chart.setOption(option); + const listData = getECModel(chart).getSeries()[0].getData(); + expect(listData.getDimension(1)).toEqual('AAA'); + expect(listData.getDimension(2)).toEqual('BBB'); + expect(listData.getDimension(3)).toEqual('CCC'); + }); + + [{ + transform: + { type: 'filter', config: { dimension: 'product', '!=': 'XXX' } } + }, { + transform: [ + { type: 'filter', config: { dimension: 'product', '!=': 'XXX' } }, + { type: 'filter', config: { dimension: 'product', '!=': 'XXX' } } + ] + }].forEach((dataset1, itIdx) => { + it(`seriesLayoutBy_changed_transform_detection_${itIdx}`, function () { + const option: EChartsFullOption = { + dataset: [{ + source: makeDatasetSourceDetection() + }, dataset1], + xAxis: { type: 'category' }, + yAxis: {}, + series: { type: 'bar', datasetIndex: 1, seriesLayoutBy: 'row' } + }; + + chart.setOption(option); + const listData = getECModel(chart).getSeries()[0].getData(); + expect(listData.getDimension(1)).toEqual('AAA'); + expect(listData.getDimension(2)).toEqual('BBB'); + expect(listData.getDimension(3)).toEqual('CCC'); + expect(listData.get('product', 0)).toEqual(0); + expect(retrieveRawValue(listData, 0, 'product')).toEqual('2012'); + expect(listData.get('product', 1)).toEqual(1); + expect(retrieveRawValue(listData, 1, 'product')).toEqual('2013'); + }); + }); + + [{ + transform: + { type: 'filter', config: { dimension: 0, '!=': 'XXX' } } + }, { + transform: [ + { type: 'filter', config: { dimension: 0, '!=': 'XXX' } }, + { type: 'filter', config: { dimension: 0, '!=': 'XXX' } } + ] + }].forEach((dataset1, itIdx) => { + it(`seriesLayoutBy_changed_transform_non_detection_${itIdx}`, function () { + const sourceWrap = makeDatasetSourceNonDetectionByRow(); + const option: EChartsFullOption = { + dataset: [{ + dimensions: sourceWrap.dimensions, + source: sourceWrap.source + }, dataset1], + xAxis: {}, + yAxis: {}, + series: { type: 'bar', datasetIndex: 1, seriesLayoutBy: 'row' } + }; + + chart.setOption(option); + const listData = getECModel(chart).getSeries()[0].getData(); + expect(listData.get(listData.getDimension(0), 0)).toEqual(41.1); + expect(listData.get(listData.getDimension(0), 1)).toEqual(30.4); + }); + }); + + [{ + transform: + { type: 'filter', config: { dimension: '2012', '!=': 'XXX' } } + }, { + transform: [ + { type: 'filter', config: { dimension: '2012', '!=': 'XXX' } }, + { type: 'filter', config: { dimension: '2012', '!=': 'XXX' } } + ] + }].forEach((dataset1, itIdx) => { + it(`inherit_detected_dimensions_${itIdx}`, function () { + const option: EChartsFullOption = { + dataset: [{ + source: makeDatasetSourceDetection() + }, dataset1], + xAxis: { type: 'category' }, + yAxis: {}, + series: { type: 'bar', datasetIndex: 1 } + }; + + chart.setOption(option); + const listData = getECModel(chart).getSeries()[0].getData(); + expect(listData.getDimension(0)).toEqual('product'); + expect(listData.getDimension(1)).toEqual('2012'); + expect(listData.getDimension(2)).toEqual('2013'); + }); + }); + + +}); + diff --git a/test/ut/spec/model/Global.test.ts b/test/ut/spec/model/Global.test.ts index 399f6537d471f50ce079ae70c68dcbe76a0fd1bd..ff26ed5af2ff7d266cc5621649ac3d037043d6be 100755 --- a/test/ut/spec/model/Global.test.ts +++ b/test/ut/spec/model/Global.test.ts @@ -19,7 +19,7 @@ */ import { EChartsType } from '../../../../src/echarts'; -import { createChart } from '../../core/utHelper'; +import { createChart, getECModel } from '../../core/utHelper'; import { ComponentMainType, ParsedValue } from '../../../../src/util/types'; import SeriesModel from '../../../../src/model/Series'; import ComponentModel from '../../../../src/model/Component'; @@ -39,11 +39,11 @@ describe('modelAndOptionMapping', function () { } function getSeries(chart: EChartsType, seriesIndex: number): SeriesModel { - return chart.getModel().getComponent('series', seriesIndex) as SeriesModel; + return getECModel(chart).getComponent('series', seriesIndex) as SeriesModel; } function getModel(chart: EChartsType, type: ComponentMainType, index: number): ComponentModel { - return chart.getModel().getComponent(type, index); + return getECModel(chart).getComponent(type, index); } function countSeries(chart: EChartsType): number { @@ -54,7 +54,7 @@ describe('modelAndOptionMapping', function () { // FIXME // access private // @ts-ignore - return chart.getModel()._componentsMap.get(type).length; + return getECModel(chart)._componentsMap.get(type).length; } function getChartView(chart: EChartsType, series: SeriesModel): ChartView { @@ -801,14 +801,14 @@ describe('modelAndOptionMapping', function () { ] }; chart.setOption(option); - expect(chart.getModel().option.backgroundColor).toEqual('rgba(1,1,1,1)'); + expect(getECModel(chart).option.backgroundColor).toEqual('rgba(1,1,1,1)'); // Not merge chart.setOption({ backgroundColor: '#fff' }, true); - expect(chart.getModel().option.backgroundColor).toEqual('#fff'); + expect(getECModel(chart).option.backgroundColor).toEqual('#fff'); }); it('innerId', function () { diff --git a/test/ut/spec/model/timelineMediaOptions.test.ts b/test/ut/spec/model/timelineMediaOptions.test.ts index 2751a14fc61b153d39baa61273a3bccf76cb9be0..8bcb3425103dca865f8d938f1be2750bfc6800e3 100755 --- a/test/ut/spec/model/timelineMediaOptions.test.ts +++ b/test/ut/spec/model/timelineMediaOptions.test.ts @@ -23,7 +23,7 @@ import SeriesModel from '../../../../src/model/Series'; import { ParsedValue } from '../../../../src/util/types'; import { LegendOption } from '../../../../src/component/legend/LegendModel'; import TimelineModel from '../../../../src/component/timeline/TimelineModel'; -import { createChart } from '../../core/utHelper'; +import { createChart, getECModel } from '../../core/utHelper'; import { EChartsFullOption } from '../../../../src/option'; @@ -33,13 +33,13 @@ describe('timelineMediaOptions', function () { return getSeries(chart, seriesIndex).getData().get('y', 0); } function getSeries(chart: EChartsType, seriesIndex: number): SeriesModel { - return chart.getModel().getComponent('series', seriesIndex) as SeriesModel; + return getECModel(chart).getComponent('series', seriesIndex) as SeriesModel; } function getLegendOption(chart: EChartsType): LegendOption { - return chart.getModel().getComponent('legend', 0).option; + return getECModel(chart).getComponent('legend', 0).option; } function getTimelineComponent(chart: EChartsType): TimelineModel { - return chart.getModel().getComponent('timeline', 0) as TimelineModel; + return getECModel(chart).getComponent('timeline', 0) as TimelineModel; } let chart: EChartsType; @@ -388,7 +388,7 @@ describe('timelineMediaOptions', function () { }; chart.setOption(option); - let ecModel = chart.getModel(); + let ecModel = getECModel(chart); expect(getData0(chart, 0)).toEqual(1111); expect(getData0(chart, 1)).toEqual(2222); @@ -410,7 +410,7 @@ describe('timelineMediaOptions', function () { }] }); - ecModel = chart.getModel(); + ecModel = getECModel(chart); const optionGotten = ecModel.getOption(); expect(optionGotten.backgroundColor).toEqual('#987654'); expect(getData0(chart, 0)).toEqual(1111); diff --git a/test/ut/spec/scale/interval.test.ts b/test/ut/spec/scale/interval.test.ts index c151ad0079bb362b142480b7b6c2ad27f9b41bd9..81aed8f0319140fbf5879543a7f4f2b2bbe00bb9 100755 --- a/test/ut/spec/scale/interval.test.ts +++ b/test/ut/spec/scale/interval.test.ts @@ -18,7 +18,7 @@ * under the License. */ -import { createChart, isValueFinite } from '../../core/utHelper'; +import { createChart, isValueFinite, getECModel } from '../../core/utHelper'; import { EChartsType } from '../../../../src/echarts'; import CartesianAxisModel from '../../../../src/coord/cartesian/AxisModel'; import IntervalScale from '../../../../src/scale/Interval'; @@ -57,7 +57,7 @@ describe('scale_interval', function () { series: [{type: 'line', data: []}] }); - const yAxis = chart.getModel().getComponent('yAxis', 0) as CartesianAxisModel; + const yAxis = getECModel(chart).getComponent('yAxis', 0) as CartesianAxisModel; const scale = yAxis.axis.scale; const ticks = scale.getTicks(); @@ -91,7 +91,7 @@ describe('scale_interval', function () { ] }); - const yAxis = chart.getModel().getComponent('yAxis', 0) as CartesianAxisModel; + const yAxis = getECModel(chart).getComponent('yAxis', 0) as CartesianAxisModel; const scale = yAxis.axis.scale as IntervalScale; const ticks = scale.getTicks(); const labels = yAxis.axis.getViewLabels().map(function (item) {