From e2dac51319842f3a7d31aeeaee7b2a7774d09703 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Thu, 23 Apr 2020 17:39:39 +0800 Subject: [PATCH 01/10] WIP(bar-racing): sort axis --- src/coord/Axis.ts | 3 ++- src/coord/cartesian/AxisModel.ts | 4 +++- src/coord/cartesian/Grid.ts | 41 ++++++++++++++++++++++++++++++++ src/scale/Ordinal.ts | 19 ++++++++++++++- 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/coord/Axis.ts b/src/coord/Axis.ts index 0870273240..ee92be7660 100644 --- a/src/coord/Axis.ts +++ b/src/coord/Axis.ts @@ -121,6 +121,7 @@ class Axis { dataToCoord(data: ScaleDataValue, clamp?: boolean): number { let extent = this._extent; const scale = this.scale; + // data = (scale instanceof OrdinalScale && typeof data === 'number') ? scale.getSortedDataIndex(data) : data; data = scale.normalize(data); if (this.onBand && scale.type === 'ordinal') { @@ -178,7 +179,7 @@ class Axis { const ticksCoords = map(ticks, function (tickValue) { return { coord: this.dataToCoord(tickValue), - tickValue: tickValue + tickValue: this.scale instanceof OrdinalScale ? this.scale.getSortedDataIndex(tickValue) : tickValue }; }, this); diff --git a/src/coord/cartesian/AxisModel.ts b/src/coord/cartesian/AxisModel.ts index d8e4aee87e..d36faec41c 100644 --- a/src/coord/cartesian/AxisModel.ts +++ b/src/coord/cartesian/AxisModel.ts @@ -35,6 +35,7 @@ interface CartesianAxisOption extends AxisBaseOption { position?: CartesianAxisPosition; // Offset is for multiple axis on the same position. offset?: number; + sort?: boolean; } class CartesianAxisModel extends ComponentModel @@ -76,7 +77,8 @@ zrUtil.mixin(CartesianAxisModel, AxisModelCommonMixin); const extraOption: CartesianAxisOption = { // gridIndex: 0, // gridId: '', - offset: 0 + offset: 0, + sort: false }; axisModelCreator('x', CartesianAxisModel, extraOption); diff --git a/src/coord/cartesian/Grid.ts b/src/coord/cartesian/Grid.ts index 0b0f2dd359..e598e506a8 100644 --- a/src/coord/cartesian/Grid.ts +++ b/src/coord/cartesian/Grid.ts @@ -48,6 +48,7 @@ import {CoordinateSystemMaster} from '../CoordinateSystem'; import { ScaleDataValue } from '../../util/types'; import List from '../../data/List'; import SeriesModel from '../../model/Series'; +import OrdinalScale from '../../scale/Ordinal'; type Cartesian2DDimensionName = 'x' | 'y'; @@ -411,6 +412,9 @@ class Grid implements CoordinateSystemMaster { each(this._axesList, function (axis) { axis.scale.setExtent(Infinity, -Infinity); }); + const sortedDataValue: number[] = []; + const sortedDataIndex: number[] = []; + ecModel.eachSeries(function (seriesModel) { if (isCartesian2D(seriesModel)) { const axesModels = findAxesModels(seriesModel); @@ -433,6 +437,11 @@ class Grid implements CoordinateSystemMaster { if (data.type === 'list') { unionExtent(data, xAxis); unionExtent(data, yAxis); + if (!sortedDataIndex.length) { + // Only sort by the first series + sortCategory(data, xAxis, xAxisModel, yAxis); + sortCategory(data, yAxis, yAxisModel, xAxis); + } } } }, this); @@ -447,6 +456,38 @@ class Grid implements CoordinateSystemMaster { ); }); } + + function sortCategory( + data: List, + axis: Axis2D, + axisModel: CartesianAxisModel, + otherAxis: Axis2D + ): void { + const sort = axisModel.get('sort'); + if (axis.type === 'category' && sort) { + data.each(otherAxis.dim, value => { + for (let i = 0, len = sortedDataValue.length; i < len; ++i) { + if (value > sortedDataValue[i]) { + sortedDataValue.splice(i, 0, value as number); + + for (let j = 0; j <= len; ++j) { + if (sortedDataIndex[j] >= i) { + ++sortedDataIndex[j]; + } + } + sortedDataIndex.push(i); + console.log(sortedDataValue, sortedDataIndex); + return; + } + } + // Smallest for now, insert at the end + sortedDataValue.push(value as number); + sortedDataIndex.push(sortedDataIndex.length); + }); + console.log(sortedDataValue, sortedDataIndex); + (axis.scale as OrdinalScale).setSortedDataIndices(sortedDataIndex); + } + } } /** diff --git a/src/scale/Ordinal.ts b/src/scale/Ordinal.ts index f7057b7d36..b28b76cfee 100644 --- a/src/scale/Ordinal.ts +++ b/src/scale/Ordinal.ts @@ -39,6 +39,7 @@ class OrdinalScale extends Scale { readonly type = 'ordinal'; private _ordinalMeta: OrdinalMeta; + private _sortedDataIndices: number[]; constructor(setting?: { @@ -54,6 +55,7 @@ class OrdinalScale extends Scale { ordinalMeta = new OrdinalMeta({categories: ordinalMeta}); } this._ordinalMeta = ordinalMeta; + this._sortedDataIndices = []; this._extent = this.getSetting('extent') || [0, ordinalMeta.categories.length - 1]; } @@ -74,10 +76,12 @@ class OrdinalScale extends Scale { * Normalize given rank or name to linear [0, 1] */ normalize(val: OrdinalRawValue | OrdinalNumber): number { - return scaleHelper.normalize(this.parse(val), this._extent); + val = this.getSortedDataIndex(this.parse(val)); + return scaleHelper.normalize(val, this._extent); } scale(val: number): OrdinalNumber { + val = this.getSortedDataIndex(val); return Math.round(scaleHelper.scale(val, this._extent)); } @@ -99,6 +103,15 @@ class OrdinalScale extends Scale { return; } + getSortedDataIndex(n: OrdinalNumber): OrdinalNumber { + if (this._sortedDataIndices.length) { + return this._sortedDataIndices[n]; + } + else { + return n; + } + } + /** * Get item on rank n */ @@ -111,6 +124,10 @@ class OrdinalScale extends Scale { } } + setSortedDataIndices(index: number[]): void { + this._sortedDataIndices = index; + } + count(): number { return this._extent[1] - this._extent[0] + 1; } From e25651747847114d0b072e003aaaa948f4e9a38d Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 6 May 2020 15:10:22 +0800 Subject: [PATCH 02/10] WIP(bar-race): most logic without label animation --- src/action/axisOrderChanged.ts | 47 +++++++++ src/chart/bar.ts | 1 + src/chart/bar/BarView.ts | 141 +++++++++++++++++++++++++-- src/coord/Axis.ts | 2 +- src/coord/cartesian/Axis2D.ts | 21 ++++- src/coord/cartesian/AxisModel.ts | 7 +- src/coord/cartesian/Grid.ts | 27 ++++-- src/scale/Ordinal.ts | 22 ++--- src/util/graphic.ts | 17 ++-- test/bar-race.html | 157 +++++++++++++++++++++++++++++++ 10 files changed, 402 insertions(+), 40 deletions(-) create mode 100644 src/action/axisOrderChanged.ts create mode 100644 test/bar-race.html diff --git a/src/action/axisOrderChanged.ts b/src/action/axisOrderChanged.ts new file mode 100644 index 0000000000..dc10708f77 --- /dev/null +++ b/src/action/axisOrderChanged.ts @@ -0,0 +1,47 @@ +/* +* 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. +*/ + +// @ts-nocheck + +import * as echarts from '../echarts'; +import * as zrUtil from 'zrender/src/core/util'; + + +/** + * @payload + * @property {string} [componentType=series] + * @property {number} [dx] + * @property {number} [dy] + * @property {number} [zoom] + * @property {number} [originX] + * @property {number} [originY] + */ +echarts.registerAction({ + type: 'axisOrderChanged', + event: 'axisOrderChanged', + update: 'update' +}, function (payload, ecModel) { + const componentType = payload.componentType || 'series'; + + ecModel.eachComponent( + { mainType: componentType, query: payload }, + function (componentModel) { + } + ); +}); diff --git a/src/chart/bar.ts b/src/chart/bar.ts index db4bbef3bf..ac28c11ac1 100644 --- a/src/chart/bar.ts +++ b/src/chart/bar.ts @@ -24,6 +24,7 @@ import {layout, largeLayout} from '../layout/barGrid'; import '../coord/cartesian/Grid'; import './bar/BarSeries'; import './bar/BarView'; +import '../action/axisOrderChanged'; // In case developer forget to include grid component import '../component/gridSimple'; diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts index 9fb9ce2796..74857a5b82 100644 --- a/src/chart/bar/BarView.ts +++ b/src/chart/bar/BarView.ts @@ -35,10 +35,10 @@ import {throttle} from '../../util/throttle'; import {createClipPath} from '../helper/createClipPathFromCoordSys'; import Sausage from '../../util/shape/sausage'; import ChartView from '../../view/Chart'; -import List from '../../data/List'; +import List, {DefaultDataVisual} from '../../data/List'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../ExtensionAPI'; -import { StageHandlerProgressParams, ZRElementEvent, ColorString } from '../../util/types'; +import { StageHandlerProgressParams, ZRElementEvent, ColorString, OrdinalNumber, Payload } from '../../util/types'; import BarSeriesModel, { BarSeriesOption, BarDataItemOption } from './BarSeries'; import type Axis2D from '../../coord/cartesian/Axis2D'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; @@ -46,6 +46,7 @@ import type { RectLike } from 'zrender/src/core/BoundingRect'; import type Model from '../../model/Model'; import { isCoordinateSystemType } from '../../coord/CoordinateSystem'; import { getDefaultLabel } from '../helper/labelHelper'; +import OrdinalScale from '../../scale/Ordinal'; const BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'borderWidth'] as const; const _eventPos = [0, 0]; @@ -138,22 +139,31 @@ class BarView extends ChartView { } _renderNormal(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { + const that = this; const group = this.group; const data = seriesModel.getData(); const oldData = this._data; const coord = seriesModel.coordinateSystem; const baseAxis = coord.getBaseAxis(); + let valueAxis: Axis2D; let isHorizontalOrRadial: boolean; if (coord.type === 'cartesian2d') { isHorizontalOrRadial = (baseAxis as Axis2D).isHorizontal(); + valueAxis = coord.getOtherAxis(baseAxis as Axis2D); } else if (coord.type === 'polar') { isHorizontalOrRadial = baseAxis.dim === 'angle'; } const animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null; + const axisAnimationModel = baseAxis.model; + + const axis2DModel = (baseAxis as Axis2D).model; + const realtimeSort = coord.type === 'cartesian2d' + && axis2DModel.get('sort') && axis2DModel.get('realtimeSort'); + let isFirstSortData = true; const needsClip = seriesModel.get('clip', true); const coordSysClipArea = getClipArea(coord, data); @@ -170,6 +180,30 @@ class BarView extends ChartView { const bgEls: BarView['_backgroundEls'] = []; const oldBgEls = this._backgroundEls; + let during: () => void = null; + if (coord.type === 'cartesian2d' && realtimeSort && isFirstSortData) { + const oldOrder = (baseAxis as Axis2D).model.option.categoryIndices; + + const orderMap = (idx: number) => { + return data.get(valueAxis.dim, idx) as number; + }; + const newOrder = this._dataSort(data, orderMap); + const orderChanged = !zrUtil.isEqualArray(oldOrder, newOrder); + + during = orderChanged + ? () => { + that._updateSort( + data, + baseAxis as Axis2D, + isHorizontalOrRadial, + ecModel, + api + ); + } + : null; + } + + data.diff(oldData) .add(function (dataIndex) { const itemModel = data.getItemModel(dataIndex); @@ -198,8 +232,10 @@ class BarView extends ChartView { } } + isFirstSortData = false; + const el = elementCreator[coord.type]( - dataIndex, layout, isHorizontalOrRadial, animationModel, false, roundCap + dataIndex, layout, isHorizontalOrRadial, animationModel, false, during, roundCap ); data.setItemGraphicEl(dataIndex, el); group.add(el); @@ -238,15 +274,49 @@ class BarView extends ChartView { } } + isFirstSortData = false; + if (el) { clearStates(el); - updateProps(el as Path, { - shape: layout - }, animationModel, newIndex); + + if (coord.type === 'cartesian2d' + && baseAxis.type === 'category' && (baseAxis as Axis2D).model.get('realtimeSort') + ) { + const rect = layout as RectShape; + let seriesShape, axisShape; + if (baseAxis.dim === 'x') { + axisShape = { + x: rect.x + }; + seriesShape = { + y: rect.y, + width: rect.width, + height: rect.height + }; + } + else { + axisShape = { + y: rect.y + }; + seriesShape = { + x: rect.x, + width: rect.width, + height: rect.height + }; + } + + updateProps(el as Path, { shape: seriesShape }, animationModel, newIndex, null, during); + updateProps(el as Path, { shape: axisShape }, axisAnimationModel, newIndex, null); + } + else { + updateProps(el as Path, { + shape: layout + }, animationModel, newIndex, null); + } } else { el = elementCreator[coord.type]( - newIndex, layout, isHorizontalOrRadial, animationModel, true, roundCap + newIndex, layout, isHorizontalOrRadial, animationModel, true, during, roundCap ); } @@ -303,6 +373,56 @@ class BarView extends ChartView { createLarge(seriesModel, this.group, true); } + _dataSort( + data: List, + map: ((idx: number) => number) + ): OrdinalNumber[] { + const dataValues: number[] = []; + const order: OrdinalNumber[] = []; + data.each(idx => { + const value = map(idx); + for (let i = 0, len = dataValues.length; i < len; ++i) { + if (dataValues[i] < value) { + dataValues.splice(i, 0, value); + for (let j = 0; j < len; ++j) { + if (order[j] >= i) { + ++order[j]; + } + } + order.push(i); + return; + } + } + dataValues.push(value); + order.push(order.length); + }); + return order; + } + + _updateSort( + data: List, + baseAxis: Axis2D, + isHorizontal: boolean, + ecModel: GlobalModel, + api: ExtensionAPI + ) { + const orderMap = (idx: number) => { + const shape = (data.getItemGraphicEl(idx) as Rect).shape; + return isHorizontal ? shape.y + shape.height : shape.x + shape.width; + } + const newOrder = this._dataSort(data, orderMap); + + const hasOrderChanged = baseAxis.setCategoryIndices(newOrder); + if (hasOrderChanged) { + const action = { + type: 'axisOrderChanged', + componentType: baseAxis.dim + 'Axis', + axisId: baseAxis.index + } as Payload; + api.dispatchAction(action); + } + } + remove(ecModel?: GlobalModel) { this._clear(ecModel); } @@ -387,7 +507,8 @@ const clip: { interface ElementCreator { ( dataIndex: number, layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean, - animationModel: BarSeriesModel, isUpdate: boolean, roundCap?: boolean + animationModel: BarSeriesModel, isUpdate: boolean, during: () => void, + roundCap?: boolean ): BarPossiblePath } @@ -397,7 +518,7 @@ const elementCreator: { cartesian2d( dataIndex, layout: RectLayout, isHorizontal, - animationModel, isUpdate + animationModel, isUpdate, during ) { const rect = new Rect({ shape: zrUtil.extend({}, layout), @@ -416,7 +537,7 @@ const elementCreator: { animateTarget[animateProperty] = layout[animateProperty]; (isUpdate ? updateProps : initProps)(rect, { shape: animateTarget - }, animationModel, dataIndex); + }, animationModel, dataIndex, null, during); } return rect; diff --git a/src/coord/Axis.ts b/src/coord/Axis.ts index ee92be7660..74a670ad47 100644 --- a/src/coord/Axis.ts +++ b/src/coord/Axis.ts @@ -179,7 +179,7 @@ class Axis { const ticksCoords = map(ticks, function (tickValue) { return { coord: this.dataToCoord(tickValue), - tickValue: this.scale instanceof OrdinalScale ? this.scale.getSortedDataIndex(tickValue) : tickValue + tickValue: this.scale instanceof OrdinalScale ? this.scale.getCategoryIndices(tickValue) : tickValue }; }, this); diff --git a/src/coord/cartesian/Axis2D.ts b/src/coord/cartesian/Axis2D.ts index 5ad717526b..327d77c8f3 100644 --- a/src/coord/cartesian/Axis2D.ts +++ b/src/coord/cartesian/Axis2D.ts @@ -18,11 +18,13 @@ */ import Axis from '../Axis'; -import { DimensionName } from '../../util/types'; +import { DimensionName, OrdinalNumber } from '../../util/types'; import Scale from '../../scale/Scale'; import CartesianAxisModel, { CartesianAxisPosition } from './AxisModel'; import Grid from './Grid'; import { OptionAxisType } from '../axisCommonTypes'; +import {isEqualArray} from 'zrender/src/core/util'; +import OrdinalScale from '../../scale/Ordinal'; interface Axis2D { @@ -110,6 +112,23 @@ class Axis2D extends Axis { return this.coordToData(this.toLocalCoord(point[this.dim === 'x' ? 0 : 1]), clamp); } + /** + * Set categoryIndices and return if has order changed + * @param newIndices new categoryIndices + * @return if the order has changed + */ + setCategoryIndices(newIndices: OrdinalNumber[], force?: boolean): boolean { + if (this.type !== 'category') { + return false; + } + + const isOrderChanged = force || !isEqualArray(this.model.option.categoryIndices, newIndices); + if (isOrderChanged) { + this.model.option.categoryIndices = newIndices; + (this.scale as OrdinalScale).setCategoryIndices(newIndices); + } + return isOrderChanged; + } } diff --git a/src/coord/cartesian/AxisModel.ts b/src/coord/cartesian/AxisModel.ts index d36faec41c..f267df455b 100644 --- a/src/coord/cartesian/AxisModel.ts +++ b/src/coord/cartesian/AxisModel.ts @@ -25,6 +25,7 @@ import Axis2D from './Axis2D'; import { AxisBaseOption } from '../axisCommonTypes'; import GridModel from './GridModel'; import { AxisBaseModel } from '../AxisBaseModel'; +import {OrdinalNumber} from '../../util/types'; export type CartesianAxisPosition = 'top' | 'bottom' | 'left' | 'right'; @@ -36,6 +37,8 @@ interface CartesianAxisOption extends AxisBaseOption { // Offset is for multiple axis on the same position. offset?: number; sort?: boolean; + realtimeSort?: boolean; + categoryIndices?: OrdinalNumber[]; } class CartesianAxisModel extends ComponentModel @@ -78,7 +81,9 @@ const extraOption: CartesianAxisOption = { // gridIndex: 0, // gridId: '', offset: 0, - sort: false + sort: false, + realtimeSort: false, + categoryIndices: [] }; axisModelCreator('x', CartesianAxisModel, extraOption); diff --git a/src/coord/cartesian/Grid.ts b/src/coord/cartesian/Grid.ts index e598e506a8..f74726e263 100644 --- a/src/coord/cartesian/Grid.ts +++ b/src/coord/cartesian/Grid.ts @@ -408,12 +408,21 @@ class Grid implements CoordinateSystemMaster { * Update cartesian properties from series. */ private _updateScale(ecModel: GlobalModel, gridModel: GridModel): void { + const sortedDataValue: number[] = []; + const sortedDataIndex: number[] = []; + let hasCategoryIndices = false; + // Reset scale each(this._axesList, function (axis) { axis.scale.setExtent(Infinity, -Infinity); + if (axis.type === 'category') { + const categoryIndices = axis.model.get('categoryIndices'); + if (categoryIndices && categoryIndices.length > 0) { + hasCategoryIndices = true; + (axis.scale as OrdinalScale).setCategoryIndices(categoryIndices); + } + } }); - const sortedDataValue: number[] = []; - const sortedDataIndex: number[] = []; ecModel.eachSeries(function (seriesModel) { if (isCartesian2D(seriesModel)) { @@ -437,11 +446,11 @@ class Grid implements CoordinateSystemMaster { if (data.type === 'list') { unionExtent(data, xAxis); unionExtent(data, yAxis); - if (!sortedDataIndex.length) { - // Only sort by the first series - sortCategory(data, xAxis, xAxisModel, yAxis); - sortCategory(data, yAxis, yAxisModel, xAxis); - } + // if (!hasCategoryIndices && !sortedDataIndex.length) { + // // Only sort by the first series + // sortCategory(data, xAxis, xAxisModel, yAxis); + // sortCategory(data, yAxis, yAxisModel, xAxis); + // } } } }, this); @@ -476,7 +485,6 @@ class Grid implements CoordinateSystemMaster { } } sortedDataIndex.push(i); - console.log(sortedDataValue, sortedDataIndex); return; } } @@ -484,8 +492,7 @@ class Grid implements CoordinateSystemMaster { sortedDataValue.push(value as number); sortedDataIndex.push(sortedDataIndex.length); }); - console.log(sortedDataValue, sortedDataIndex); - (axis.scale as OrdinalScale).setSortedDataIndices(sortedDataIndex); + (axis.scale as OrdinalScale).setCategoryIndices(sortedDataIndex); } } } diff --git a/src/scale/Ordinal.ts b/src/scale/Ordinal.ts index b28b76cfee..541cbbe88c 100644 --- a/src/scale/Ordinal.ts +++ b/src/scale/Ordinal.ts @@ -39,7 +39,7 @@ class OrdinalScale extends Scale { readonly type = 'ordinal'; private _ordinalMeta: OrdinalMeta; - private _sortedDataIndices: number[]; + private _categoryIndices: OrdinalNumber[]; constructor(setting?: { @@ -55,7 +55,7 @@ class OrdinalScale extends Scale { ordinalMeta = new OrdinalMeta({categories: ordinalMeta}); } this._ordinalMeta = ordinalMeta; - this._sortedDataIndices = []; + this._categoryIndices = []; this._extent = this.getSetting('extent') || [0, ordinalMeta.categories.length - 1]; } @@ -76,12 +76,12 @@ class OrdinalScale extends Scale { * Normalize given rank or name to linear [0, 1] */ normalize(val: OrdinalRawValue | OrdinalNumber): number { - val = this.getSortedDataIndex(this.parse(val)); + val = this.getCategoryIndices(this.parse(val)); return scaleHelper.normalize(val, this._extent); } scale(val: number): OrdinalNumber { - val = this.getSortedDataIndex(val); + val = this.getCategoryIndices(val); return Math.round(scaleHelper.scale(val, this._extent)); } @@ -103,9 +103,13 @@ class OrdinalScale extends Scale { return; } - getSortedDataIndex(n: OrdinalNumber): OrdinalNumber { - if (this._sortedDataIndices.length) { - return this._sortedDataIndices[n]; + setCategoryIndices(indices: OrdinalNumber[]): void { + this._categoryIndices = indices; + } + + getCategoryIndices(n: OrdinalNumber): OrdinalNumber { + if (this._categoryIndices.length) { + return this._categoryIndices[n]; } else { return n; @@ -124,10 +128,6 @@ class OrdinalScale extends Scale { } } - setSortedDataIndices(index: number[]): void { - this._sortedDataIndices = index; - } - count(): number { return this._extent[1] - this._extent[0] + 1; } diff --git a/src/util/graphic.ts b/src/util/graphic.ts index 9a35173106..ede01928bf 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -1041,9 +1041,11 @@ function animateOrSetProps( getAnimationDelayParams?: (el: Element, dataIndex: number) => AnimationDelayCallbackParam }, dataIndex?: number | (() => void), - cb?: () => void + cb?: () => void, + during?: () => void ) { if (typeof dataIndex === 'function') { + during = cb; cb = dataIndex; dataIndex = null; } @@ -1080,7 +1082,8 @@ function animateOrSetProps( delay: animationDelay || 0, easing: animationEasing, done: cb, - force: !!cb + force: !!cb || !!during, + during: during }) : (el.stopAnimation(), el.attr(props), cb && cb()); } @@ -1113,9 +1116,10 @@ function updateProps( // TODO: TYPE AnimatableModel animatableModel?: Model, dataIndex?: number | (() => void), - cb?: () => void + cb?: () => void, + during?: () => void ) { - animateOrSetProps(true, el, props, animatableModel, dataIndex, cb); + animateOrSetProps(true, el, props, animatableModel, dataIndex, cb, during); } export {updateProps}; @@ -1133,9 +1137,10 @@ export function initProps( props: Props, animatableModel?: Model, dataIndex?: number | (() => void), - cb?: () => void + cb?: () => void, + during?: () => void ) { - animateOrSetProps(false, el, props, animatableModel, dataIndex, cb); + animateOrSetProps(false, el, props, animatableModel, dataIndex, cb, during); } /** diff --git a/test/bar-race.html b/test/bar-race.html new file mode 100644 index 0000000000..093a51d95d --- /dev/null +++ b/test/bar-race.html @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + From 82c70aba2bfd370bf45d446dd98d73c44039a410 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Mon, 11 May 2020 15:55:03 +0800 Subject: [PATCH 03/10] feat(bar-race): one series done --- ...axisOrderChanged.ts => changeAxisOrder.ts} | 4 +- src/chart/bar.ts | 2 +- src/chart/bar/BarView.ts | 137 +++++++++++------- src/coord/Axis.ts | 2 +- src/coord/cartesian/Axis2D.ts | 17 +-- src/coord/cartesian/AxisModel.ts | 8 +- src/coord/cartesian/Grid.ts | 65 ++++----- src/scale/Ordinal.ts | 24 +-- src/util/types.ts | 4 + test/bar-race.html | 1 + 10 files changed, 147 insertions(+), 117 deletions(-) rename src/action/{axisOrderChanged.ts => changeAxisOrder.ts} (95%) diff --git a/src/action/axisOrderChanged.ts b/src/action/changeAxisOrder.ts similarity index 95% rename from src/action/axisOrderChanged.ts rename to src/action/changeAxisOrder.ts index dc10708f77..5d49d8f2b2 100644 --- a/src/action/axisOrderChanged.ts +++ b/src/action/changeAxisOrder.ts @@ -33,8 +33,8 @@ import * as zrUtil from 'zrender/src/core/util'; * @property {number} [originY] */ echarts.registerAction({ - type: 'axisOrderChanged', - event: 'axisOrderChanged', + type: 'changeAxisOrder', + event: 'changeAxisOrder', update: 'update' }, function (payload, ecModel) { const componentType = payload.componentType || 'series'; diff --git a/src/chart/bar.ts b/src/chart/bar.ts index ac28c11ac1..1370124c25 100644 --- a/src/chart/bar.ts +++ b/src/chart/bar.ts @@ -24,7 +24,7 @@ import {layout, largeLayout} from '../layout/barGrid'; import '../coord/cartesian/Grid'; import './bar/BarSeries'; import './bar/BarView'; -import '../action/axisOrderChanged'; +import '../action/changeAxisOrder'; // In case developer forget to include grid component import '../component/gridSimple'; diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts index 74857a5b82..4e3e303a76 100644 --- a/src/chart/bar/BarView.ts +++ b/src/chart/bar/BarView.ts @@ -38,7 +38,7 @@ import ChartView from '../../view/Chart'; import List, {DefaultDataVisual} from '../../data/List'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../ExtensionAPI'; -import { StageHandlerProgressParams, ZRElementEvent, ColorString, OrdinalNumber, Payload } from '../../util/types'; +import { StageHandlerProgressParams, ZRElementEvent, ColorString, OrdinalSortInfo, Payload, OrdinalNumber } from '../../util/types'; import BarSeriesModel, { BarSeriesOption, BarDataItemOption } from './BarSeries'; import type Axis2D from '../../coord/cartesian/Axis2D'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; @@ -161,9 +161,9 @@ class BarView extends ChartView { const axisAnimationModel = baseAxis.model; const axis2DModel = (baseAxis as Axis2D).model; - const realtimeSort = coord.type === 'cartesian2d' - && axis2DModel.get('sort') && axis2DModel.get('realtimeSort'); - let isFirstSortData = true; + const axisSort = coord.type === 'cartesian2d' && axis2DModel.get('sort') + && axis2DModel.get('sortSeriesIndex') === seriesModel.seriesIndex; + const realtimeSort = axisSort && axis2DModel.get('realtimeSort'); const needsClip = seriesModel.get('clip', true); const coordSysClipArea = getClipArea(coord, data); @@ -181,26 +181,30 @@ class BarView extends ChartView { const oldBgEls = this._backgroundEls; let during: () => void = null; - if (coord.type === 'cartesian2d' && realtimeSort && isFirstSortData) { - const oldOrder = (baseAxis as Axis2D).model.option.categoryIndices; - + if (coord.type === 'cartesian2d') { + const oldOrder = (baseAxis.scale as OrdinalScale).getCategorySortInfo(); const orderMap = (idx: number) => { return data.get(valueAxis.dim, idx) as number; }; - const newOrder = this._dataSort(data, orderMap); - const orderChanged = !zrUtil.isEqualArray(oldOrder, newOrder); - - during = orderChanged - ? () => { - that._updateSort( - data, - baseAxis as Axis2D, - isHorizontalOrRadial, - ecModel, - api - ); - } - : null; + + if (realtimeSort) { + // Sort in animation during + const isOrderChanged = this._isDataOrderChanged(data, orderMap, oldOrder); + during = isOrderChanged + ? () => { + const orderMap = (idx: number) => { + const shape = (data.getItemGraphicEl(idx) as Rect).shape; + return isHorizontalOrRadial ? shape.y + shape.height : shape.x + shape.width; + }; + that._updateSort(data, orderMap, baseAxis as Axis2D, api); + } + : null; + + } + else if (axisSort) { + // Sort now in the first frame + this._updateSort(data, orderMap, baseAxis as Axis2D, api); + } } @@ -232,8 +236,6 @@ class BarView extends ChartView { } } - isFirstSortData = false; - const el = elementCreator[coord.type]( dataIndex, layout, isHorizontalOrRadial, animationModel, false, during, roundCap ); @@ -274,13 +276,11 @@ class BarView extends ChartView { } } - isFirstSortData = false; - if (el) { clearStates(el); if (coord.type === 'cartesian2d' - && baseAxis.type === 'category' && (baseAxis as Axis2D).model.get('realtimeSort') + && baseAxis.type === 'category' && (baseAxis as Axis2D).model.get('sort') ) { const rect = layout as RectShape; let seriesShape, axisShape; @@ -376,46 +376,73 @@ class BarView extends ChartView { _dataSort( data: List, map: ((idx: number) => number) - ): OrdinalNumber[] { - const dataValues: number[] = []; - const order: OrdinalNumber[] = []; + ): OrdinalSortInfo[] { + type SortValueInfo = { + mappedValue: number, + ordinalNumber: OrdinalNumber, + beforeSortIndex: number + }; + const info: SortValueInfo[] = []; data.each(idx => { - const value = map(idx); - for (let i = 0, len = dataValues.length; i < len; ++i) { - if (dataValues[i] < value) { - dataValues.splice(i, 0, value); - for (let j = 0; j < len; ++j) { - if (order[j] >= i) { - ++order[j]; - } - } - order.push(i); - return; - } - } - dataValues.push(value); - order.push(order.length); + info.push({ + mappedValue: map(idx), + ordinalNumber: idx, + beforeSortIndex: null + }); + }); + + info.sort((a, b) => { + return b.mappedValue - a.mappedValue; + }); + + // Update beforeSortIndex + for (let i = 0; i < info.length; ++i) { + info[info[i].ordinalNumber].beforeSortIndex = i; + } + return info.map(item => { + return { + ordinalNumber: item.ordinalNumber, + beforeSortIndex: item.beforeSortIndex + }; }); - return order; + } + + _isDataOrderChanged( + data: List, + orderMap: ((idx: number) => number), + oldOrder: OrdinalSortInfo[] + ): boolean { + if (!oldOrder || oldOrder.length === 0) { + // If no old order, return if has new data + return data.count() !== 0; + } + + let lastValue = Number.MAX_VALUE; + for (let i = 0; i < oldOrder.length; ++i) { + const value = orderMap(oldOrder[i].ordinalNumber); + if (value > lastValue) { + return true; + } + lastValue = value; + } + return false; } _updateSort( data: List, + orderMap: ((idx: number) => number), baseAxis: Axis2D, - isHorizontal: boolean, - ecModel: GlobalModel, api: ExtensionAPI ) { - const orderMap = (idx: number) => { - const shape = (data.getItemGraphicEl(idx) as Rect).shape; - return isHorizontal ? shape.y + shape.height : shape.x + shape.width; - } - const newOrder = this._dataSort(data, orderMap); + const oldOrder = (baseAxis.scale as OrdinalScale).getCategorySortInfo(); + const isOrderChanged = this._isDataOrderChanged(data, orderMap, oldOrder); + if (isOrderChanged) { + // re-sort and update in axis + const sortInfo = this._dataSort(data, orderMap); + baseAxis.setCategorySortInfo(sortInfo); - const hasOrderChanged = baseAxis.setCategoryIndices(newOrder); - if (hasOrderChanged) { const action = { - type: 'axisOrderChanged', + type: 'changeAxisOrder', componentType: baseAxis.dim + 'Axis', axisId: baseAxis.index } as Payload; diff --git a/src/coord/Axis.ts b/src/coord/Axis.ts index 74a670ad47..6ad302405a 100644 --- a/src/coord/Axis.ts +++ b/src/coord/Axis.ts @@ -179,7 +179,7 @@ class Axis { const ticksCoords = map(ticks, function (tickValue) { return { coord: this.dataToCoord(tickValue), - tickValue: this.scale instanceof OrdinalScale ? this.scale.getCategoryIndices(tickValue) : tickValue + tickValue: this.scale instanceof OrdinalScale ? this.scale.getCategoryIndex(tickValue) : tickValue }; }, this); diff --git a/src/coord/cartesian/Axis2D.ts b/src/coord/cartesian/Axis2D.ts index 327d77c8f3..179ef6ae0a 100644 --- a/src/coord/cartesian/Axis2D.ts +++ b/src/coord/cartesian/Axis2D.ts @@ -18,7 +18,7 @@ */ import Axis from '../Axis'; -import { DimensionName, OrdinalNumber } from '../../util/types'; +import { DimensionName, OrdinalSortInfo } from '../../util/types'; import Scale from '../../scale/Scale'; import CartesianAxisModel, { CartesianAxisPosition } from './AxisModel'; import Grid from './Grid'; @@ -113,21 +113,16 @@ class Axis2D extends Axis { } /** - * Set categoryIndices and return if has order changed - * @param newIndices new categoryIndices - * @return if the order has changed + * Set ordinalSortInfo + * @param info new OrdinalSortInfo */ - setCategoryIndices(newIndices: OrdinalNumber[], force?: boolean): boolean { + setCategorySortInfo(info: OrdinalSortInfo[]): boolean { if (this.type !== 'category') { return false; } - const isOrderChanged = force || !isEqualArray(this.model.option.categoryIndices, newIndices); - if (isOrderChanged) { - this.model.option.categoryIndices = newIndices; - (this.scale as OrdinalScale).setCategoryIndices(newIndices); - } - return isOrderChanged; + this.model.option.categorySortInfo = info; + (this.scale as OrdinalScale).setCategorySortInfo(info); } } diff --git a/src/coord/cartesian/AxisModel.ts b/src/coord/cartesian/AxisModel.ts index f267df455b..fffcf5c0ec 100644 --- a/src/coord/cartesian/AxisModel.ts +++ b/src/coord/cartesian/AxisModel.ts @@ -25,7 +25,7 @@ import Axis2D from './Axis2D'; import { AxisBaseOption } from '../axisCommonTypes'; import GridModel from './GridModel'; import { AxisBaseModel } from '../AxisBaseModel'; -import {OrdinalNumber} from '../../util/types'; +import {OrdinalSortInfo} from '../../util/types'; export type CartesianAxisPosition = 'top' | 'bottom' | 'left' | 'right'; @@ -38,7 +38,8 @@ interface CartesianAxisOption extends AxisBaseOption { offset?: number; sort?: boolean; realtimeSort?: boolean; - categoryIndices?: OrdinalNumber[]; + sortSeriesIndex?: number; + categorySortInfo?: OrdinalSortInfo[]; } class CartesianAxisModel extends ComponentModel @@ -83,7 +84,8 @@ const extraOption: CartesianAxisOption = { offset: 0, sort: false, realtimeSort: false, - categoryIndices: [] + sortSeriesIndex: null, + categorySortInfo: [] }; axisModelCreator('x', CartesianAxisModel, extraOption); diff --git a/src/coord/cartesian/Grid.ts b/src/coord/cartesian/Grid.ts index f74726e263..8e9d85318e 100644 --- a/src/coord/cartesian/Grid.ts +++ b/src/coord/cartesian/Grid.ts @@ -416,11 +416,8 @@ class Grid implements CoordinateSystemMaster { each(this._axesList, function (axis) { axis.scale.setExtent(Infinity, -Infinity); if (axis.type === 'category') { - const categoryIndices = axis.model.get('categoryIndices'); - if (categoryIndices && categoryIndices.length > 0) { - hasCategoryIndices = true; - (axis.scale as OrdinalScale).setCategoryIndices(categoryIndices); - } + const categorySortInfo = axis.model.get('categorySortInfo'); + (axis.scale as OrdinalScale).setCategorySortInfo(categorySortInfo); } }); @@ -466,35 +463,35 @@ class Grid implements CoordinateSystemMaster { }); } - function sortCategory( - data: List, - axis: Axis2D, - axisModel: CartesianAxisModel, - otherAxis: Axis2D - ): void { - const sort = axisModel.get('sort'); - if (axis.type === 'category' && sort) { - data.each(otherAxis.dim, value => { - for (let i = 0, len = sortedDataValue.length; i < len; ++i) { - if (value > sortedDataValue[i]) { - sortedDataValue.splice(i, 0, value as number); - - for (let j = 0; j <= len; ++j) { - if (sortedDataIndex[j] >= i) { - ++sortedDataIndex[j]; - } - } - sortedDataIndex.push(i); - return; - } - } - // Smallest for now, insert at the end - sortedDataValue.push(value as number); - sortedDataIndex.push(sortedDataIndex.length); - }); - (axis.scale as OrdinalScale).setCategoryIndices(sortedDataIndex); - } - } + // function sortCategory( + // data: List, + // axis: Axis2D, + // axisModel: CartesianAxisModel, + // otherAxis: Axis2D + // ): void { + // const sort = axisModel.get('sort'); + // if (axis.type === 'category' && sort) { + // data.each(otherAxis.dim, value => { + // for (let i = 0, len = sortedDataValue.length; i < len; ++i) { + // if (value > sortedDataValue[i]) { + // sortedDataValue.splice(i, 0, value as number); + + // for (let j = 0; j <= len; ++j) { + // if (sortedDataIndex[j] >= i) { + // ++sortedDataIndex[j]; + // } + // } + // sortedDataIndex.push(i); + // return; + // } + // } + // // Smallest for now, insert at the end + // sortedDataValue.push(value as number); + // sortedDataIndex.push(sortedDataIndex.length); + // }); + // (axis.scale as OrdinalScale).setCategoryIndices(sortedDataIndex); + // } + // } } /** diff --git a/src/scale/Ordinal.ts b/src/scale/Ordinal.ts index 541cbbe88c..7a9d2b38f2 100644 --- a/src/scale/Ordinal.ts +++ b/src/scale/Ordinal.ts @@ -28,7 +28,7 @@ import Scale from './Scale'; import OrdinalMeta from '../data/OrdinalMeta'; import List from '../data/List'; import * as scaleHelper from './helper'; -import { OrdinalRawValue, OrdinalNumber, DimensionLoose } from '../util/types'; +import { OrdinalRawValue, OrdinalNumber, DimensionLoose, OrdinalSortInfo } from '../util/types'; import { AxisBaseOption } from '../coord/axisCommonTypes'; import { isArray } from 'zrender/src/core/util'; @@ -39,7 +39,7 @@ class OrdinalScale extends Scale { readonly type = 'ordinal'; private _ordinalMeta: OrdinalMeta; - private _categoryIndices: OrdinalNumber[]; + private _categorySortInfo: OrdinalSortInfo[]; constructor(setting?: { @@ -55,7 +55,7 @@ class OrdinalScale extends Scale { ordinalMeta = new OrdinalMeta({categories: ordinalMeta}); } this._ordinalMeta = ordinalMeta; - this._categoryIndices = []; + this._categorySortInfo = []; this._extent = this.getSetting('extent') || [0, ordinalMeta.categories.length - 1]; } @@ -76,12 +76,12 @@ class OrdinalScale extends Scale { * Normalize given rank or name to linear [0, 1] */ normalize(val: OrdinalRawValue | OrdinalNumber): number { - val = this.getCategoryIndices(this.parse(val)); + val = this.getCategoryIndex(this.parse(val)); return scaleHelper.normalize(val, this._extent); } scale(val: number): OrdinalNumber { - val = this.getCategoryIndices(val); + val = this.getCategoryIndex(val); return Math.round(scaleHelper.scale(val, this._extent)); } @@ -103,13 +103,17 @@ class OrdinalScale extends Scale { return; } - setCategoryIndices(indices: OrdinalNumber[]): void { - this._categoryIndices = indices; + setCategorySortInfo(info: OrdinalSortInfo[]): void { + this._categorySortInfo = info; } - getCategoryIndices(n: OrdinalNumber): OrdinalNumber { - if (this._categoryIndices.length) { - return this._categoryIndices[n]; + getCategorySortInfo(): OrdinalSortInfo[] { + return this._categorySortInfo; + } + + getCategoryIndex(n: OrdinalNumber): OrdinalNumber { + if (this._categorySortInfo.length) { + return this._categorySortInfo[n].beforeSortIndex; } else { return n; diff --git a/src/util/types.ts b/src/util/types.ts index 0503ec892c..47caf6840f 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -242,6 +242,10 @@ export type TooltipRenderMode = 'html' | 'richText'; // Check `convertDataValue` for more details. export type OrdinalRawValue = string | number; export type OrdinalNumber = number; // The number mapped from each OrdinalRawValue. +export type OrdinalSortInfo = { + ordinalNumber: OrdinalNumber, + beforeSortIndex: number +}; export type ParsedValueNumeric = number | OrdinalNumber; export type ParsedValue = ParsedValueNumeric | OrdinalRawValue; // FIXME:TS better name? diff --git a/test/bar-race.html b/test/bar-race.html index 093a51d95d..3bbd01028b 100644 --- a/test/bar-race.html +++ b/test/bar-race.html @@ -88,6 +88,7 @@ data: ['A', 'B', 'C', 'D'], sort: true, realtimeSort: true, + sortSeriesIndex: 0, inverse: true, axisLabel: { formatter: function (label, id) { From a3afee5ec1e06f60ee438998df9d95c212bb405d Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 13 May 2020 14:12:33 +0800 Subject: [PATCH 04/10] WIP(bar-race): update during --- src/chart/bar/BarSeries.ts | 4 +- src/chart/bar/BarView.ts | 96 +++++++++++++++++++++++++---------- src/coord/Axis.ts | 1 - src/model/mixin/dataFormat.ts | 29 ++++++++++- src/util/types.ts | 1 + 5 files changed, 100 insertions(+), 31 deletions(-) diff --git a/src/chart/bar/BarSeries.ts b/src/chart/bar/BarSeries.ts index 7d3c9b9424..b9fc6d8a5f 100644 --- a/src/chart/bar/BarSeries.ts +++ b/src/chart/bar/BarSeries.ts @@ -71,7 +71,9 @@ export interface BarSeriesOption extends BaseBarSeriesOption, SeriesStackOptionM data?: (BarDataItemOption | BarDataValue)[] - label?: LabelOption + label?: LabelOption & { + precision?: 'auto' | number + } itemStyle?: BarItemStyleOption diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts index 4e3e303a76..6421e5dcc3 100644 --- a/src/chart/bar/BarView.ts +++ b/src/chart/bar/BarView.ts @@ -30,6 +30,7 @@ import { clearStates } from '../../util/graphic'; import Path, { PathProps } from 'zrender/src/graphic/Path'; +import * as numberUtil from '../../util/number'; import Group from 'zrender/src/graphic/Group'; import {throttle} from '../../util/throttle'; import {createClipPath} from '../helper/createClipPathFromCoordSys'; @@ -38,7 +39,7 @@ import ChartView from '../../view/Chart'; import List, {DefaultDataVisual} from '../../data/List'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../ExtensionAPI'; -import { StageHandlerProgressParams, ZRElementEvent, ColorString, OrdinalSortInfo, Payload, OrdinalNumber } from '../../util/types'; +import { StageHandlerProgressParams, ZRElementEvent, ColorString, OrdinalSortInfo, Payload, OrdinalNumber, OrdinalRawValue, DisplayState } from '../../util/types'; import BarSeriesModel, { BarSeriesOption, BarDataItemOption } from './BarSeries'; import type Axis2D from '../../coord/cartesian/Axis2D'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; @@ -47,6 +48,8 @@ import type Model from '../../model/Model'; import { isCoordinateSystemType } from '../../coord/CoordinateSystem'; import { getDefaultLabel } from '../helper/labelHelper'; import OrdinalScale from '../../scale/Ordinal'; +import AngleAxis from '../../coord/polar/AngleAxis'; +import RadiusAxis from '../../coord/polar/RadiusAxis'; const BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'borderWidth'] as const; const _eventPos = [0, 0]; @@ -100,9 +103,11 @@ class BarView extends ChartView { _backgroundEls: (Rect | Sector)[]; - render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { + render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) { this._updateDrawMode(seriesModel); + seriesModel.setAnimatedValues([]); + const coordinateSystemType = seriesModel.get('coordinateSystem'); if (coordinateSystemType === 'cartesian2d' @@ -146,7 +151,7 @@ class BarView extends ChartView { const coord = seriesModel.coordinateSystem; const baseAxis = coord.getBaseAxis(); - let valueAxis: Axis2D; + let valueAxis: Axis2D | RadiusAxis | AngleAxis; let isHorizontalOrRadial: boolean; if (coord.type === 'cartesian2d') { @@ -155,6 +160,7 @@ class BarView extends ChartView { } else if (coord.type === 'polar') { isHorizontalOrRadial = baseAxis.dim === 'angle'; + valueAxis = coord.getOtherAxis(baseAxis as (AngleAxis | RadiusAxis)); } const animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null; @@ -180,7 +186,10 @@ class BarView extends ChartView { const bgEls: BarView['_backgroundEls'] = []; const oldBgEls = this._backgroundEls; - let during: () => void = null; + let hasDuringForOneData = false; + let getDuring: () => (() => void) = () => { + return null; + }; if (coord.type === 'cartesian2d') { const oldOrder = (baseAxis.scale as OrdinalScale).getCategorySortInfo(); const orderMap = (idx: number) => { @@ -188,17 +197,40 @@ class BarView extends ChartView { }; if (realtimeSort) { + let precision = seriesModel.get(['label', 'precision']); + if (precision == null || precision === 'auto') { + precision = 0; + } + // Sort in animation during const isOrderChanged = this._isDataOrderChanged(data, orderMap, oldOrder); - during = isOrderChanged - ? () => { - const orderMap = (idx: number) => { - const shape = (data.getItemGraphicEl(idx) as Rect).shape; - return isHorizontalOrRadial ? shape.y + shape.height : shape.x + shape.width; - }; - that._updateSort(data, orderMap, baseAxis as Axis2D, api); - } - : null; + if (isOrderChanged) { + getDuring = () => { + if (!hasDuringForOneData) { + hasDuringForOneData = true; + return () => { + const orderMap = (idx: number) => { + const shape = (data.getItemGraphicEl(idx) as Rect).shape; + return isHorizontalOrRadial ? shape.y + shape.height : shape.x + shape.width; + }; + that._updateSort(data, orderMap, baseAxis as Axis2D, api); + + // Update labels + data.eachItemGraphicEl((el: Rect, dataIndex) => { + const value = numberUtil.round(el.__value, precision as number, true); + seriesModel.setAnimatedValue(dataIndex, value); + const label = seriesModel.getFormattedLabel(dataIndex, 'normal'); + const text = el.getTextContent(); + text.style.text = label; + text.dirty(); + }); + }; + } + else { + return () => null; + } + }; + } } else if (axisSort) { @@ -236,8 +268,9 @@ class BarView extends ChartView { } } + const dataValue = data.get(valueAxis.dim, dataIndex); const el = elementCreator[coord.type]( - dataIndex, layout, isHorizontalOrRadial, animationModel, false, during, roundCap + seriesModel, 0, dataValue, dataIndex, layout, isHorizontalOrRadial, itemModel, animationModel, false, getDuring(), roundCap ); data.setItemGraphicEl(dataIndex, el); group.add(el); @@ -250,6 +283,8 @@ class BarView extends ChartView { .update(function (newIndex, oldIndex) { const itemModel = data.getItemModel(newIndex); const layout = getLayout[coord.type](data, newIndex, itemModel); + const newValue = data.get(valueAxis.dim, newIndex); + const oldValue = data.get(valueAxis.dim, oldIndex); if (drawBackground) { const bgEl = oldBgEls[oldIndex]; @@ -305,7 +340,7 @@ class BarView extends ChartView { }; } - updateProps(el as Path, { shape: seriesShape }, animationModel, newIndex, null, during); + updateProps(el as Path, { shape: seriesShape }, animationModel, newIndex, null, getDuring()); updateProps(el as Path, { shape: axisShape }, axisAnimationModel, newIndex, null); } else { @@ -316,7 +351,7 @@ class BarView extends ChartView { } else { el = elementCreator[coord.type]( - newIndex, layout, isHorizontalOrRadial, animationModel, true, during, roundCap + seriesModel, oldValue, newValue, newIndex, layout, isHorizontalOrRadial, itemModel, animationModel, true, getDuring(), roundCap ); } @@ -412,9 +447,9 @@ class BarView extends ChartView { orderMap: ((idx: number) => number), oldOrder: OrdinalSortInfo[] ): boolean { - if (!oldOrder || oldOrder.length === 0) { - // If no old order, return if has new data - return data.count() !== 0; + const oldCount = oldOrder ? oldOrder.length : 0; + if (oldCount !== data.count()) { + return true; } let lastValue = Number.MAX_VALUE; @@ -533,8 +568,9 @@ const clip: { interface ElementCreator { ( - dataIndex: number, layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean, - animationModel: BarSeriesModel, isUpdate: boolean, during: () => void, + seriesModel: BarSeriesModel, oldValue: OrdinalRawValue, newValue: OrdinalRawValue, dataIndex: number, + layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean, + itemModel: Model, animationModel: BarSeriesModel, isUpdate: boolean, during: () => void, roundCap?: boolean ): BarPossiblePath } @@ -544,8 +580,8 @@ const elementCreator: { } = { cartesian2d( - dataIndex, layout: RectLayout, isHorizontal, - animationModel, isUpdate, during + seriesModel, oldValue, newValue, dataIndex, layout: RectLayout, isHorizontal, + itemModel, animationModel, isUpdate, during ) { const rect = new Rect({ shape: zrUtil.extend({}, layout), @@ -562,8 +598,11 @@ const elementCreator: { const animateTarget = {} as RectShape; rectShape[animateProperty] = 0; animateTarget[animateProperty] = layout[animateProperty]; + rect.__value = typeof oldValue === 'string' ? parseInt(oldValue, 10) : oldValue; + (isUpdate ? updateProps : initProps)(rect, { - shape: animateTarget + shape: animateTarget, + __value: typeof newValue === 'string' ? parseInt(newValue, 10) : newValue }, animationModel, dataIndex, null, during); } @@ -571,8 +610,8 @@ const elementCreator: { }, polar( - dataIndex: number, layout: SectorLayout, isRadial: boolean, - animationModel, isUpdate, roundCap + seriesModel, oldValue, newValue, dataIndex, layout: SectorLayout, isRadial: boolean, + itemModel, animationModel, isUpdate, during, roundCap ) { // Keep the same logic with bar in catesion: use end value to control // direction. Notice that if clockwise is true (by default), the sector @@ -597,8 +636,9 @@ const elementCreator: { sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle; animateTarget[animateProperty] = layout[animateProperty]; (isUpdate ? updateProps : initProps)(sector, { - shape: animateTarget - }, animationModel, dataIndex); + shape: animateTarget, + // __value: typeof dataValue === 'string' ? parseInt(dataValue, 10) : dataValue + }, animationModel); } return sector; diff --git a/src/coord/Axis.ts b/src/coord/Axis.ts index 6ad302405a..698e322d01 100644 --- a/src/coord/Axis.ts +++ b/src/coord/Axis.ts @@ -121,7 +121,6 @@ class Axis { dataToCoord(data: ScaleDataValue, clamp?: boolean): number { let extent = this._extent; const scale = this.scale; - // data = (scale instanceof OrdinalScale && typeof data === 'number') ? scale.getSortedDataIndex(data) : data; data = scale.normalize(data); if (this.onBand && scale.type === 'ordinal') { diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts index eca285f0e6..d0c32280de 100644 --- a/src/model/mixin/dataFormat.ts +++ b/src/model/mixin/dataFormat.ts @@ -19,7 +19,7 @@ import {retrieveRawValue} from '../../data/helper/dataProvider'; import {formatTpl} from '../../util/format'; -import { DataHost, DisplayState, TooltipRenderMode, CallbackDataParams, ColorString, ZRColor } from '../../util/types'; +import { DataHost, DisplayState, TooltipRenderMode, CallbackDataParams, ColorString, ZRColor, OptionDataValue } from '../../util/types'; import GlobalModel from '../Global'; import Element from 'zrender/src/Element'; @@ -33,6 +33,7 @@ interface DataFormatMixin extends DataHost { componentIndex: number; id: string; name: string; + animatedValue: OptionDataValue[]; } class DataFormatMixin { @@ -48,6 +49,7 @@ class DataFormatMixin { const data = this.getData(dataType); const rawValue = this.getRawValue(dataIndex, dataType); + const animatedValue = this.getAnimatedValue(dataIndex); const rawDataIndex = data.getRawIndex(dataIndex); const name = data.getName(dataIndex); const itemOpt = data.getRawDataItem(dataIndex); @@ -71,6 +73,7 @@ class DataFormatMixin { data: itemOpt, dataType: dataType, value: rawValue, + animatedValue: animatedValue, color: color, borderColor: borderColor, dimensionNames: userOutput ? userOutput.dimensionNames : null, @@ -143,6 +146,30 @@ class DataFormatMixin { return retrieveRawValue(this.getData(dataType), idx); } + setAnimatedValues( + dataValue: OptionDataValue[] + ): void { + this.animatedValue = dataValue.slice(); + } + + setAnimatedValue( + idx: number, + dataValue: OptionDataValue + ): void { + this.animatedValue[idx] = dataValue; + } + + getAnimatedValue( + idx: number + ): OptionDataValue { + if (!this.animatedValue || this.animatedValue.length === 0) { + return null; + } + else { + return this.animatedValue[idx]; + } + } + /** * Should be implemented. * @param {number} dataIndex diff --git a/src/util/types.ts b/src/util/types.ts index 47caf6840f..7b140c68ca 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -464,6 +464,7 @@ export interface CallbackDataParams { data: any; dataType?: string; value: any; + animatedValue?: any; color?: ZRColor; borderColor?: string; dimensionNames?: DimensionName[]; From 027b1be059c0bfe013f38d11e90e955e687e70a9 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 13 May 2020 14:34:33 +0800 Subject: [PATCH 05/10] WIP(bar-race): update animation --- src/chart/bar/BarView.ts | 10 +++++----- src/coord/cartesian/Axis2D.ts | 1 - test/bar-race.html | 11 +++++++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts index 6421e5dcc3..a397687d2b 100644 --- a/src/chart/bar/BarView.ts +++ b/src/chart/bar/BarView.ts @@ -321,22 +321,22 @@ class BarView extends ChartView { let seriesShape, axisShape; if (baseAxis.dim === 'x') { axisShape = { - x: rect.x + x: rect.x, + width: rect.width }; seriesShape = { y: rect.y, - width: rect.width, height: rect.height }; } else { axisShape = { - y: rect.y + y: rect.y, + height: rect.height }; seriesShape = { x: rect.x, - width: rect.width, - height: rect.height + width: rect.width }; } diff --git a/src/coord/cartesian/Axis2D.ts b/src/coord/cartesian/Axis2D.ts index 179ef6ae0a..ca82100095 100644 --- a/src/coord/cartesian/Axis2D.ts +++ b/src/coord/cartesian/Axis2D.ts @@ -23,7 +23,6 @@ import Scale from '../../scale/Scale'; import CartesianAxisModel, { CartesianAxisPosition } from './AxisModel'; import Grid from './Grid'; import { OptionAxisType } from '../axisCommonTypes'; -import {isEqualArray} from 'zrender/src/core/util'; import OrdinalScale from '../../scale/Ordinal'; diff --git a/test/bar-race.html b/test/bar-race.html index 3bbd01028b..ff7e12fd7b 100644 --- a/test/bar-race.html +++ b/test/bar-race.html @@ -105,7 +105,12 @@ label: { show: true, position: 'right', - formatter: '{c}元' + // formatter: '{c}元', + formatter: function (params) { + // console.log(params); + return params.animatedValue + '元'; + }, + valueAnimation: true } // }, { // name: 'Y', @@ -129,7 +134,9 @@ legend: { show: true }, - animationDurationUpdate: 10000 + animationDuration: 10000, + animationDurationUpdate: 10000, + animationEasing: 'linear' }; chart.setOption(option); From 5b1485a4eebe4dfe4a7a46d39eec94fbe68af295 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Wed, 10 Jun 2020 19:05:17 +0800 Subject: [PATCH 06/10] feat(bar-race): label value animation --- src/chart/bar/BarView.ts | 65 +++++++++----------- src/model/mixin/dataFormat.ts | 39 +++--------- src/util/graphic.ts | 112 +++++++++++++++++++++++++++++++++- src/util/types.ts | 3 +- 4 files changed, 149 insertions(+), 70 deletions(-) diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts index c01da61a13..297f20b057 100644 --- a/src/chart/bar/BarView.ts +++ b/src/chart/bar/BarView.ts @@ -27,7 +27,9 @@ import { initProps, enableHoverEmphasis, setLabelStyle, - clearStates + clearStates, + updateLabel, + initLabel } from '../../util/graphic'; import Path, { PathProps } from 'zrender/src/graphic/Path'; import * as numberUtil from '../../util/number'; @@ -107,16 +109,15 @@ class BarView extends ChartView { render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) { this._updateDrawMode(seriesModel); - seriesModel.setAnimatedValues([]); - const coordinateSystemType = seriesModel.get('coordinateSystem'); + const isReorder = payload && payload.type === 'changeAxisOrder'; if (coordinateSystemType === 'cartesian2d' || coordinateSystemType === 'polar' ) { this._isLargeDraw ? this._renderLarge(seriesModel, ecModel, api) - : this._renderNormal(seriesModel, ecModel, api); + : this._renderNormal(seriesModel, ecModel, api, isReorder); } else if (__DEV__) { console.warn('Only cartesian2d and polar supported for bar.'); @@ -144,7 +145,12 @@ class BarView extends ChartView { } } - private _renderNormal(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI): void { + private _renderNormal( + seriesModel: BarSeriesModel, + ecModel: GlobalModel, + api: ExtensionAPI, + isReorder: boolean + ): void { const that = this; const group = this.group; const data = seriesModel.getData(); @@ -179,6 +185,8 @@ class BarView extends ChartView { // We don't use clipPath in normal mode because we needs a perfect animation // And don't want the label are clipped. + const labelModel = seriesModel.getModel('label'); + const roundCap = seriesModel.get('roundCap', true); const drawBackground = seriesModel.get('showBackground', true); @@ -199,11 +207,6 @@ class BarView extends ChartView { }; if (realtimeSort) { - let precision = seriesModel.get(['label', 'precision']); - if (precision == null || precision === 'auto') { - precision = 0; - } - // Sort in animation during const isOrderChanged = this._isDataOrderChanged(data, orderMap, oldOrder); if (isOrderChanged) { @@ -216,16 +219,6 @@ class BarView extends ChartView { return isHorizontalOrRadial ? shape.y + shape.height : shape.x + shape.width; }; that._updateSort(data, orderMap, baseAxis as Axis2D, api); - - // Update labels - data.eachItemGraphicEl((el: Rect, dataIndex) => { - const value = numberUtil.round(el.__value, precision as number, true); - seriesModel.setAnimatedValue(dataIndex, value); - const label = seriesModel.getFormattedLabel(dataIndex, 'normal'); - const text = el.getTextContent(); - text.style.text = label; - text.dirty(); - }); }; } else { @@ -233,7 +226,6 @@ class BarView extends ChartView { } }; } - } else if (axisSort) { // Sort now in the first frame @@ -273,9 +265,8 @@ class BarView extends ChartView { } } - const dataValue = data.get(valueAxis.dim, dataIndex); const el = elementCreator[coord.type]( - seriesModel, 0, dataValue, dataIndex, layout, isHorizontalOrRadial, itemModel, animationModel, false, getDuring(), roundCap + seriesModel, data, dataIndex, layout, isHorizontalOrRadial, animationModel, false, getDuring(), roundCap ); data.setItemGraphicEl(dataIndex, el); group.add(el); @@ -350,7 +341,9 @@ class BarView extends ChartView { }; } - updateProps(el as Path, { shape: seriesShape }, animationModel, newIndex, null, getDuring()); + if (!isReorder) { + updateProps(el as Path, { shape: seriesShape }, animationModel, newIndex, null, getDuring()); + } updateProps(el as Path, { shape: axisShape }, axisAnimationModel, newIndex, null); } else { @@ -358,10 +351,11 @@ class BarView extends ChartView { shape: layout }, animationModel, newIndex, null); } + updateLabel(el, data, newIndex, labelModel, seriesModel, animationModel); } else { el = elementCreator[coord.type]( - seriesModel, oldValue, newValue, newIndex, layout, isHorizontalOrRadial, itemModel, animationModel, true, getDuring(), roundCap + seriesModel, data, newIndex, layout, isHorizontalOrRadial, animationModel, true, getDuring(), roundCap ); } @@ -581,9 +575,9 @@ const clip: { interface ElementCreator { ( - seriesModel: BarSeriesModel, oldValue: OrdinalRawValue, newValue: OrdinalRawValue, dataIndex: number, + seriesModel: BarSeriesModel, data: List, newIndex: number, layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean, - itemModel: Model, animationModel: BarSeriesModel, isUpdate: boolean, during: () => void, + animationModel: BarSeriesModel, isUpdate: boolean, during: () => void, roundCap?: boolean ): BarPossiblePath } @@ -593,8 +587,8 @@ const elementCreator: { } = { cartesian2d( - seriesModel, oldValue, newValue, dataIndex, layout: RectLayout, isHorizontal, - itemModel, animationModel, isUpdate, during + seriesModel, data, newIndex, layout: RectLayout, isHorizontal, + animationModel, isUpdate, during ) { const rect = new Rect({ shape: zrUtil.extend({}, layout), @@ -611,20 +605,21 @@ const elementCreator: { const animateTarget = {} as RectShape; rectShape[animateProperty] = 0; animateTarget[animateProperty] = layout[animateProperty]; - rect.__value = typeof oldValue === 'string' ? parseInt(oldValue, 10) : oldValue; (isUpdate ? updateProps : initProps)(rect, { - shape: animateTarget, - __value: typeof newValue === 'string' ? parseInt(newValue, 10) : newValue - }, animationModel, dataIndex, null, during); + shape: animateTarget + }, animationModel, newIndex, null, during); + + const labelModel = seriesModel.getModel('label'); + (isUpdate ? updateLabel : initLabel)(rect, data, newIndex, labelModel, seriesModel, animationModel); } return rect; }, polar( - seriesModel, oldValue, newValue, dataIndex, layout: SectorLayout, isRadial: boolean, - itemModel, animationModel, isUpdate, during, roundCap + seriesModel, data, newIndex, layout: SectorLayout, isRadial: boolean, + animationModel, isUpdate, during, roundCap ) { // Keep the same logic with bar in catesion: use end value to control // direction. Notice that if clockwise is true (by default), the sector diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts index d0c32280de..31e35b555a 100644 --- a/src/model/mixin/dataFormat.ts +++ b/src/model/mixin/dataFormat.ts @@ -17,11 +17,12 @@ * under the License. */ +import * as zrUtil from 'zrender/src/core/util'; +import Element from 'zrender/src/Element'; import {retrieveRawValue} from '../../data/helper/dataProvider'; import {formatTpl} from '../../util/format'; import { DataHost, DisplayState, TooltipRenderMode, CallbackDataParams, ColorString, ZRColor, OptionDataValue } from '../../util/types'; import GlobalModel from '../Global'; -import Element from 'zrender/src/Element'; const DIMENSION_LABEL_REG = /\{@(.+?)\}/g; @@ -44,12 +45,12 @@ class DataFormatMixin { getDataParams( dataIndex: number, dataType?: string, - el?: Element // May be used in override. + el?: Element, // May be used in override. + rawValue?: unknown ): CallbackDataParams { const data = this.getData(dataType); - const rawValue = this.getRawValue(dataIndex, dataType); - const animatedValue = this.getAnimatedValue(dataIndex); + rawValue = rawValue || this.getRawValue(dataIndex, dataType); const rawDataIndex = data.getRawIndex(dataIndex); const name = data.getName(dataIndex); const itemOpt = data.getRawDataItem(dataIndex); @@ -73,7 +74,6 @@ class DataFormatMixin { data: itemOpt, dataType: dataType, value: rawValue, - animatedValue: animatedValue, color: color, borderColor: borderColor, dimensionNames: userOutput ? userOutput.dimensionNames : null, @@ -99,13 +99,14 @@ class DataFormatMixin { status?: DisplayState, dataType?: string, dimIndex?: number, - labelProp?: string + labelProp?: string, + rawValue?: unknown ): string { status = status || 'normal'; const data = this.getData(dataType); const itemModel = data.getItemModel(dataIndex); - const params = this.getDataParams(dataIndex, dataType); + const params = this.getDataParams(dataIndex, dataType, null, rawValue); if (dimIndex != null && (params.value instanceof Array)) { params.value = params.value[dimIndex]; } @@ -146,30 +147,6 @@ class DataFormatMixin { return retrieveRawValue(this.getData(dataType), idx); } - setAnimatedValues( - dataValue: OptionDataValue[] - ): void { - this.animatedValue = dataValue.slice(); - } - - setAnimatedValue( - idx: number, - dataValue: OptionDataValue - ): void { - this.animatedValue[idx] = dataValue; - } - - getAnimatedValue( - idx: number - ): OptionDataValue { - if (!this.animatedValue || this.animatedValue.length === 0) { - return null; - } - else { - return this.animatedValue[idx]; - } - } - /** * Should be implemented. * @param {number} dataIndex diff --git a/src/util/graphic.ts b/src/util/graphic.ts index a0a133b6da..4b8569218c 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -46,7 +46,7 @@ import LRU from 'zrender/src/core/LRU'; import Displayable, { DisplayableProps } from 'zrender/src/graphic/Displayable'; import { PatternObject } from 'zrender/src/graphic/Pattern'; import { GradientObject } from 'zrender/src/graphic/Gradient'; -import Element, { ElementEvent, ElementTextConfig } from 'zrender/src/Element'; +import Element, { ElementEvent, ElementTextConfig, ElementProps } from 'zrender/src/Element'; import Model from '../model/Model'; import { AnimationOptionMixin, @@ -58,7 +58,9 @@ import { ColorString, DataModel, ECEventData, - ZRStyleProps + ZRStyleProps, + SeriesOption, + ParsedValue } from './types'; import GlobalModel from '../model/Global'; import { makeInner } from './model'; @@ -72,6 +74,11 @@ import { map, defaults } from 'zrender/src/core/util'; +import * as numberUtil from './number'; +import SeriesModel from '../model/Series'; +import {OnframeCallback} from 'zrender/src/animation/Animator'; +import List from '../data/List'; +import DataFormatMixin from '../model/mixin/dataFormat'; const mathMax = Math.max; @@ -1057,7 +1064,7 @@ function animateOrSetProps( }, dataIndex?: number | (() => void), cb?: () => void, - during?: () => void + during?: OnframeCallback ) { if (typeof dataIndex === 'function') { during = cb; @@ -1158,6 +1165,105 @@ export function initProps( animateOrSetProps(false, el, props, animatableModel, dataIndex, cb, during); } +function animateOrSetLabel( + isUpdate: boolean, + el: Element, + data: List, + dataIndex: number, + labelModel: Model, + seriesModel: SeriesModel, + animatableModel?: Model +) { + const element = el as Element & { __value: (string | number)[] | number }; + const valueAnimationEnabled = labelModel && labelModel.get('valueAnimation'); + if (valueAnimationEnabled) { + let precision = labelModel.get('precision') || 0; + + let interpolateValues: (number | string)[] | (number | string); + const rawValues = seriesModel.getRawValue(dataIndex); + let isRawValueNumber = false; + if (typeof rawValues === 'number') { + isRawValueNumber = true; + interpolateValues = rawValues; + } + else { + interpolateValues = []; + for (let i = 0; i < (rawValues as []).length; ++i) { + const info = data.getDimensionInfo(i); + if (info.type !== 'ordinal') { + interpolateValues.push((rawValues as [])[i]); + } + } + } + + const props = { + __value: interpolateValues + } as ElementProps; + + if (!element.__value) { + // Init with 0 for non-ordinal dims + if (isRawValueNumber) { + element.__value = 0; + } + else { + const initValues = []; + for (let i = 0; i < (interpolateValues as []).length; ++i) { + initValues[i] = 0; + } + element.__value = initValues; + } + } + + const during = (target: Props, percent: number) => { + let interpolated; + if (isRawValueNumber) { + interpolated = numberUtil.round(target.__value as number, precision); + } + else { + interpolated = []; + for (let i = 0, j = 0; i < (rawValues as []).length; ++i) { + const info = data.getDimensionInfo(i); + // Don't interpolate ordinal dims + if (info.type === 'ordinal') { + interpolated[i] = (rawValues as [])[i]; + } + else { + interpolated[i] = numberUtil.round((target.__value as [])[j], precision); + ++j; + } + } + } + const text = el.getTextContent(); + text.style.text = seriesModel.getFormattedLabel(dataIndex, 'normal', null, null, null, interpolated); + text.dirty(); + }; + + animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, null, during); + } +} + +export function updateLabel( + el: Element, + data: List, + dataIndex: number, + labelModel: Model, + seriesModel: SeriesModel, + animatableModel?: Model +) { + animateOrSetLabel(true, el, data, dataIndex, labelModel, seriesModel, animatableModel); +} + +export function initLabel( + el: Element, + data: List, + dataIndex: number, + labelModel: Model, + seriesModel: SeriesModel, + animatableModel?: Model +) { + animateOrSetLabel(false, el, data, dataIndex, labelModel, seriesModel, animatableModel); +} + /** * Get transform matrix of target (param target), * in coordinate of its ancestor (param ancestor) diff --git a/src/util/types.ts b/src/util/types.ts index 018c5a7e2b..a2c7ee41e1 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -464,7 +464,6 @@ export interface CallbackDataParams { data: any; dataType?: string; value: any; - animatedValue?: any; color?: ZRColor; borderColor?: string; dimensionNames?: DimensionName[]; @@ -767,6 +766,8 @@ export interface LabelOption extends TextCommonOption { overflow?: TextStyleProps['overflow'] silent?: boolean + precision?: number + valueAnimation?: boolean // TODO: TYPE not all label support formatter // formatter?: string | ((params: CallbackDataParams) => string) From 4d41640c8234cd0f0e8f49a7c26072c11c0cd391 Mon Sep 17 00:00:00 2001 From: Ovilia Date: Fri, 12 Jun 2020 14:47:14 +0800 Subject: [PATCH 07/10] feat(bar-race): label formatter --- src/chart/bar/BarView.ts | 26 ++++++++--- src/chart/helper/labelHelper.ts | 11 +++-- src/data/helper/dataProvider.ts | 7 +-- src/model/mixin/dataFormat.ts | 6 +-- src/util/graphic.ts | 81 ++++++++++++++++++++------------- test/bar-race.html | 41 +++-------------- 6 files changed, 91 insertions(+), 81 deletions(-) diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts index 297f20b057..c1deb1990f 100644 --- a/src/chart/bar/BarView.ts +++ b/src/chart/bar/BarView.ts @@ -41,7 +41,7 @@ import ChartView from '../../view/Chart'; import List, {DefaultDataVisual} from '../../data/List'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../ExtensionAPI'; -import { StageHandlerProgressParams, ZRElementEvent, ColorString, OrdinalSortInfo, Payload, OrdinalNumber, OrdinalRawValue, DisplayState } from '../../util/types'; +import { StageHandlerProgressParams, ZRElementEvent, ColorString, OrdinalSortInfo, Payload, OrdinalNumber, OrdinalRawValue, DisplayState, ParsedValue } from '../../util/types'; import BarSeriesModel, { BarSeriesOption, BarDataItemOption } from './BarSeries'; import type Axis2D from '../../coord/cartesian/Axis2D'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; @@ -215,8 +215,14 @@ class BarView extends ChartView { hasDuringForOneData = true; return () => { const orderMap = (idx: number) => { - const shape = (data.getItemGraphicEl(idx) as Rect).shape; - return isHorizontalOrRadial ? shape.y + shape.height : shape.x + shape.width; + const el = (data.getItemGraphicEl(idx) as Rect); + if (el) { + const shape = el.shape; + return isHorizontalOrRadial ? shape.y + shape.height : shape.x + shape.width; + } + else { + return 0; + } }; that._updateSort(data, orderMap, baseAxis as Axis2D, api); }; @@ -279,8 +285,6 @@ class BarView extends ChartView { .update(function (newIndex, oldIndex) { const itemModel = data.getItemModel(newIndex); const layout = getLayout[coord.type](data, newIndex, itemModel); - const newValue = data.get(valueAxis.dim, newIndex); - const oldValue = data.get(valueAxis.dim, oldIndex); if (drawBackground) { const bgEl = oldBgEls[oldIndex]; @@ -351,7 +355,11 @@ class BarView extends ChartView { shape: layout }, animationModel, newIndex, null); } - updateLabel(el, data, newIndex, labelModel, seriesModel, animationModel); + + const defaultTextGetter = (values: ParsedValue | ParsedValue[]) => { + return getDefaultLabel(seriesModel.getData(), newIndex, values); + }; + updateLabel(el, data, newIndex, labelModel, seriesModel, animationModel, defaultTextGetter); } else { el = elementCreator[coord.type]( @@ -610,8 +618,12 @@ const elementCreator: { shape: animateTarget }, animationModel, newIndex, null, during); + const defaultTextGetter = (values: ParsedValue | ParsedValue[]) => { + return getDefaultLabel(seriesModel.getData(), newIndex, values); + }; + const labelModel = seriesModel.getModel('label'); - (isUpdate ? updateLabel : initLabel)(rect, data, newIndex, labelModel, seriesModel, animationModel); + (isUpdate ? updateLabel : initLabel)(rect, data, newIndex, labelModel, seriesModel, animationModel, defaultTextGetter); } return rect; diff --git a/src/chart/helper/labelHelper.ts b/src/chart/helper/labelHelper.ts index 25a0960cb1..118e4b86fe 100644 --- a/src/chart/helper/labelHelper.ts +++ b/src/chart/helper/labelHelper.ts @@ -20,22 +20,27 @@ import {retrieveRawValue} from '../../data/helper/dataProvider'; import List from '../../data/List'; +import {ParsedValue} from '../../util/types'; /** * @return label string. Not null/undefined */ -export function getDefaultLabel(data: List, dataIndex: number): string { +export function getDefaultLabel( + data: List, + dataIndex: number, + interpolatedValues?: ParsedValue | ParsedValue[] +): string { const labelDims = data.mapDimensionsAll('defaultedLabel'); const len = labelDims.length; // Simple optimization (in lots of cases, label dims length is 1) if (len === 1) { - return retrieveRawValue(data, dataIndex, labelDims[0]); + return retrieveRawValue(data, dataIndex, labelDims[0], interpolatedValues); } else if (len) { const vals = []; for (let i = 0; i < labelDims.length; i++) { - const val = retrieveRawValue(data, dataIndex, labelDims[i]); + const val = retrieveRawValue(data, dataIndex, labelDims[i], interpolatedValues); vals.push(val); } return vals.join(' '); diff --git a/src/data/helper/dataProvider.ts b/src/data/helper/dataProvider.ts index c499f0643b..2bdee2a0d4 100644 --- a/src/data/helper/dataProvider.ts +++ b/src/data/helper/dataProvider.ts @@ -35,7 +35,7 @@ import { SERIES_LAYOUT_BY_COLUMN, SERIES_LAYOUT_BY_ROW, DimensionName, DimensionIndex, OptionSourceData, - DimensionIndexLoose, OptionDataItem, OptionDataValue + DimensionIndexLoose, OptionDataItem, OptionDataValue, ParsedValue } from '../../util/types'; import List from '../List'; @@ -314,14 +314,15 @@ function getRawValueSimply( // value may be 0.91000000001, which have brings trouble to display. // TODO: consider how to treat null/undefined/NaN when display? export function retrieveRawValue( - data: List, dataIndex: number, dim?: DimensionName | DimensionIndexLoose + data: List, dataIndex: number, dim?: DimensionName | DimensionIndexLoose, + interpolatedValues?: ParsedValue | ParsedValue[] ): any { if (!data) { return; } // Consider data may be not persistent. - const dataItem = data.getRawDataItem(dataIndex); + const dataItem = interpolatedValues || data.getRawDataItem(dataIndex); if (dataItem == null) { return; diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts index 31e35b555a..ecc6468d58 100644 --- a/src/model/mixin/dataFormat.ts +++ b/src/model/mixin/dataFormat.ts @@ -21,7 +21,7 @@ import * as zrUtil from 'zrender/src/core/util'; import Element from 'zrender/src/Element'; import {retrieveRawValue} from '../../data/helper/dataProvider'; import {formatTpl} from '../../util/format'; -import { DataHost, DisplayState, TooltipRenderMode, CallbackDataParams, ColorString, ZRColor, OptionDataValue } from '../../util/types'; +import { DataHost, DisplayState, TooltipRenderMode, CallbackDataParams, ColorString, ZRColor, OptionDataValue, ParsedValue } from '../../util/types'; import GlobalModel from '../Global'; const DIMENSION_LABEL_REG = /\{@(.+?)\}/g; @@ -100,13 +100,13 @@ class DataFormatMixin { dataType?: string, dimIndex?: number, labelProp?: string, - rawValue?: unknown + interpolateValues?: ParsedValue | ParsedValue[] ): string { status = status || 'normal'; const data = this.getData(dataType); const itemModel = data.getItemModel(dataIndex); - const params = this.getDataParams(dataIndex, dataType, null, rawValue); + const params = this.getDataParams(dataIndex, dataType, null, interpolateValues); if (dimIndex != null && (params.value instanceof Array)) { params.value = params.value[dimIndex]; } diff --git a/src/util/graphic.ts b/src/util/graphic.ts index 4b8569218c..480ca7dc8c 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -621,7 +621,8 @@ interface SetLabelStyleOpt extends TextCommonParams { state: DisplayState, dataType: string, labelDimIndex: number, - labelProp: string + labelProp: string, + interpolateValues?: ParsedValue | ParsedValue[] ) => string }, labelDataIndex?: LDI, @@ -630,6 +631,31 @@ interface SetLabelStyleOpt extends TextCommonParams { } +function getLabelText(opt?: SetLabelStyleOpt, interpolateValues?: ParsedValue | ParsedValue[]) { + const labelFetcher = opt.labelFetcher; + const labelDataIndex = opt.labelDataIndex; + const labelDimIndex = opt.labelDimIndex; + const labelProp = opt.labelProp; + + let baseText; + if (labelFetcher) { + baseText = labelFetcher.getFormattedLabel(labelDataIndex, 'normal', null, labelDimIndex, labelProp, interpolateValues); + } + if (baseText == null) { + baseText = isFunction(opt.defaultText) ? opt.defaultText(labelDataIndex, opt) : opt.defaultText; + } + const emphasisStyleText = retrieve2( + labelFetcher + ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', null, labelDimIndex, labelProp) + : null, + baseText + ); + return { + normal: baseText, + emphasis: emphasisStyleText + }; +} + /** * Set normal styles and emphasis styles about text on target element * If target is a ZRText. It will create a new style object. @@ -659,26 +685,6 @@ export function setLabelStyle( // label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`. let richText = isSetOnText ? targetEl as ZRText : null; if (showNormal || showEmphasis) { - const labelFetcher = opt.labelFetcher; - const labelDataIndex = opt.labelDataIndex; - const labelDimIndex = opt.labelDimIndex; - const labelProp = opt.labelProp; - - let baseText; - if (labelFetcher) { - baseText = labelFetcher.getFormattedLabel(labelDataIndex, 'normal', null, labelDimIndex, labelProp); - } - if (baseText == null) { - baseText = isFunction(opt.defaultText) ? opt.defaultText(labelDataIndex, opt) : opt.defaultText; - } - const normalStyleText = baseText; - const emphasisStyleText = retrieve2( - labelFetcher - ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', null, labelDimIndex, labelProp) - : null, - baseText - ); - if (!isSetOnText) { // Reuse the previous richText = targetEl.getTextContent(); @@ -735,8 +741,9 @@ export function setLabelStyle( // auto slient is those cases. richText.silent = !!normalModel.getShallow('silent'); - normalStyle.text = normalStyleText; - emphasisState.style.text = emphasisStyleText; + const labelText = getLabelText(opt); + normalStyle.text = labelText.normal; + emphasisState.style.text = labelText.emphasis; // Keep x and y if (richText.style.x != null) { @@ -1172,9 +1179,10 @@ function animateOrSetLabel( dataIndex: number, labelModel: Model, seriesModel: SeriesModel, - animatableModel?: Model + animatableModel?: Model, + defaultTextGetter?: (value: ParsedValue[] | ParsedValue) => string ) { - const element = el as Element & { __value: (string | number)[] | number }; + const element = el as Element & { __value: ParsedValue[] | ParsedValue }; const valueAnimationEnabled = labelModel && labelModel.get('valueAnimation'); if (valueAnimationEnabled) { let precision = labelModel.get('precision') || 0; @@ -1234,8 +1242,17 @@ function animateOrSetLabel( } } const text = el.getTextContent(); - text.style.text = seriesModel.getFormattedLabel(dataIndex, 'normal', null, null, null, interpolated); - text.dirty(); + if (text) { + const labelText = getLabelText({ + labelDataIndex: dataIndex, + labelFetcher: seriesModel, + defaultText: defaultTextGetter + ? defaultTextGetter(interpolated) + : interpolated + '' + }, interpolated); + text.style.text = labelText.normal; + text.dirty(); + } }; animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, null, during); @@ -1248,9 +1265,10 @@ export function updateLabel( dataIndex: number, labelModel: Model, seriesModel: SeriesModel, - animatableModel?: Model + animatableModel?: Model, + defaultTextGetter?: (value: ParsedValue[] | ParsedValue) => string ) { - animateOrSetLabel(true, el, data, dataIndex, labelModel, seriesModel, animatableModel); + animateOrSetLabel(true, el, data, dataIndex, labelModel, seriesModel, animatableModel, defaultTextGetter); } export function initLabel( @@ -1259,9 +1277,10 @@ export function initLabel( dataIndex: number, labelModel: Model, seriesModel: SeriesModel, - animatableModel?: Model + animatableModel?: Model, + defaultTextGetter?: (value: ParsedValue[] | ParsedValue) => string ) { - animateOrSetLabel(false, el, data, dataIndex, labelModel, seriesModel, animatableModel); + animateOrSetLabel(false, el, data, dataIndex, labelModel, seriesModel, animatableModel, defaultTextGetter); } /** diff --git a/test/bar-race.html b/test/bar-race.html index ff7e12fd7b..9f1b53081f 100644 --- a/test/bar-race.html +++ b/test/bar-race.html @@ -48,8 +48,8 @@ +
-