diff --git a/src/component/visualMap/PiecewiseModel.ts b/src/component/visualMap/PiecewiseModel.ts index ef5913a62a..dd2966580a 100644 --- a/src/component/visualMap/PiecewiseModel.ts +++ b/src/component/visualMap/PiecewiseModel.ts @@ -256,6 +256,13 @@ class PiecewiseModel extends VisualMapModel { ? piece.value + '' : piece.index + ''; } + /** + * @override + */ + isCategoryMode(): boolean { + return this._mode === 'categories'; + } + /** * @public */ @@ -313,9 +320,14 @@ class PiecewiseModel extends VisualMapModel { const dataIndices: number[] = []; const data = seriesModel.getData(); - data.each(this.getDataDimensionIndex(data), function (value: number, dataIndex: number) { + const dimIndex = this.getDataDimensionIndex(data); + const ordinalMeta = this.isCategoryMode() && data.getStore().getOrdinalMeta(dimIndex); + + data.each(dimIndex, function (value: number, dataIndex: number) { // Should always base on model pieceList, because it is order sensitive. - const pIdx = VisualMapping.findPieceIndex(value, pieceList); + const pIdx = VisualMapping.findPieceIndex( + (ordinalMeta ? ordinalMeta.categories[value] : value) as number, pieceList + ); pIdx === pieceIndex && dataIndices.push(dataIndex); }, this); diff --git a/src/component/visualMap/VisualMapModel.ts b/src/component/visualMap/VisualMapModel.ts index 26262e975f..26ab32fc84 100644 --- a/src/component/visualMap/VisualMapModel.ts +++ b/src/component/visualMap/VisualMapModel.ts @@ -647,6 +647,13 @@ class VisualMapModel extends Com return null; } + /** + * Whether data values on an ordinal dimension are matched by their category strings. + */ + isCategoryMode(): boolean { + return false; + } + /** * FIXME * Do not publish to thirt-part-dev temporarily diff --git a/src/component/visualMap/visualEncoding.ts b/src/component/visualMap/visualEncoding.ts index 6f090b3694..f00fbafef0 100644 --- a/src/component/visualMap/visualEncoding.ts +++ b/src/component/visualMap/visualEncoding.ts @@ -42,7 +42,8 @@ export const visualMapEncodingHandlers: StageHandler[] = [ visualMapModel.stateList, visualMapModel.targetVisuals, zrUtil.bind(visualMapModel.getValueState, visualMapModel), - visualMapModel.getDataDimensionIndex(seriesModel.getData()) + visualMapModel.getDataDimensionIndex(seriesModel.getData()), + visualMapModel.isCategoryMode() )); }); diff --git a/src/visual/visualSolution.ts b/src/visual/visualSolution.ts index 33fffb5ee8..e72a462f6a 100644 --- a/src/visual/visualSolution.ts +++ b/src/visual/visualSolution.ts @@ -29,9 +29,11 @@ import { ParsedValue, DimensionLoose, StageHandlerProgressExecutor, - DimensionIndex + DimensionIndex, + OrdinalNumber } from '../util/types'; import SeriesData from '../data/SeriesData'; +import OrdinalMeta from '../data/OrdinalMeta'; import { getItemVisualFromData, setItemVisualFromData } from './helper'; const each = zrUtil.each; @@ -195,12 +197,14 @@ export function applyVisual( * @param visualMappings > * @param getValueState param: valueOrIndex, return: state. * @param dim dimension or dimension index. + * @param useCategory map ordinal numbers on `dim` back to their category strings. */ export function incrementalApplyVisual( stateList: readonly VisualState[], visualMappings: VisualMappingCollection, getValueState: (valueOrIndex: ParsedValue | number) => VisualState, - dim?: DimensionLoose + dim?: DimensionLoose, + useCategory?: boolean ): StageHandlerProgressExecutor { const visualTypesMap: Partial> = {}; zrUtil.each(stateList, function (state) { @@ -210,9 +214,12 @@ export function incrementalApplyVisual( return { progress: function progress(params, data) { + const store = data.getStore(); let dimIndex: DimensionIndex; + let ordinalMeta: OrdinalMeta; if (dim != null) { dimIndex = data.getDimensionIndex(dim); + ordinalMeta = useCategory && store.getOrdinalMeta(dimIndex); } function getVisual(key: string) { @@ -224,7 +231,6 @@ export function incrementalApplyVisual( } let dataIndex: number; - const store = data.getStore(); while ((dataIndex = params.next()) != null) { const rawDataItem = data.getRawDataItem(dataIndex); @@ -234,9 +240,12 @@ export function incrementalApplyVisual( continue; } - const value = dim != null + let value: ParsedValue = dim != null ? store.get(dimIndex, dataIndex) : dataIndex; + if (ordinalMeta) { + value = ordinalMeta.categories[value as OrdinalNumber]; + } const valueState = getValueState(value); const mappings = visualMappings[valueState]; diff --git a/test/ut/spec/component/visualMap/piecewiseCategories.test.ts b/test/ut/spec/component/visualMap/piecewiseCategories.test.ts new file mode 100644 index 0000000000..690be87c43 --- /dev/null +++ b/test/ut/spec/component/visualMap/piecewiseCategories.test.ts @@ -0,0 +1,80 @@ + +/* +* 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 { createChart, getECModel } from '../../../core/utHelper'; +import { EChartsType } from '../../../../../src/echarts'; +import PiecewiseModel from '../../../../../src/component/visualMap/PiecewiseModel'; + + +describe('visualMap_piecewiseCategories', function () { + let chart: EChartsType; + beforeEach(function () { + chart = createChart(); + }); + + afterEach(function () { + chart.dispose(); + }); + + // See https://github.com/apache/echarts/issues/21236 + function setOptionWithCategoryAxisDimension() { + chart.setOption({ + animation: false, + dataset: { + source: [ + ['age', 'profession'], + [30, 'teacher'], + [40, 'doctor'], + [50, 'teacher'] + ] + }, + xAxis: {type: 'value'}, + yAxis: {type: 'category'}, + visualMap: { + type: 'piecewise', + dimension: 'profession', + categories: ['teacher', 'doctor'], + inRange: { + symbol: ['diamond', 'circle'] + } + }, + series: [{type: 'scatter', encode: {x: 'age', y: 'profession'}}] + }); + } + + it('should map categories on a dimension encoded to a category axis', function () { + setOptionWithCategoryAxisDimension(); + const data = getECModel(chart).getSeriesByIndex(0).getData(); + + expect([0, 1, 2].map(idx => data.getItemVisual(idx, 'symbol'))).toEqual(['diamond', 'circle', 'diamond']); + }); + + it('should find target data of a category on a dimension encoded to a category axis', function () { + setOptionWithCategoryAxisDimension(); + const visualMapModel = getECModel(chart).getComponent('visualMap') as PiecewiseModel; + const seriesId = getECModel(chart).getSeriesByIndex(0).id; + + const pieceIndexOf = (value: string) => visualMapModel.getPieceList().findIndex(piece => piece.value === value); + + expect(visualMapModel.findTargetDataIndices(pieceIndexOf('teacher'))).toEqual([{seriesId, dataIndex: [0, 2]}]); + expect(visualMapModel.findTargetDataIndices(pieceIndexOf('doctor'))).toEqual([{seriesId, dataIndex: [1]}]); + }); + +});