未验证 提交 79fbcb92 编写于 作者: O Ovilia 提交者: GitHub

Merge pull request #12484 from apache/feat-bar-race

feat: bar race
/*
* 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: 'changeAxisOrder',
event: 'changeAxisOrder',
update: 'update'
}, function (payload, ecModel) {
const componentType = payload.componentType || 'series';
ecModel.eachComponent(
{ mainType: componentType, query: payload },
function (componentModel) {
}
);
});
......@@ -24,6 +24,7 @@ import {layout, largeLayout} from '../layout/barGrid';
import '../coord/cartesian/Grid';
import './bar/BarSeries';
import './bar/BarView';
import '../action/changeAxisOrder';
// In case developer forget to include grid component
import '../component/gridSimple';
......
......@@ -27,18 +27,21 @@ 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';
import Group from 'zrender/src/graphic/Group';
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, 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';
......@@ -46,6 +49,9 @@ 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';
import AngleAxis from '../../coord/polar/AngleAxis';
import RadiusAxis from '../../coord/polar/RadiusAxis';
const BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'borderWidth'] as const;
const BAR_BORDER_RADIUS_QUERY = ['itemStyle', 'borderRadius'] as const;
......@@ -100,17 +106,18 @@ class BarView extends ChartView {
private _backgroundEls: (Rect | Sector)[];
render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI): void {
render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) {
this._updateDrawMode(seriesModel);
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.');
......@@ -138,23 +145,38 @@ 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();
const oldData = this._data;
const coord = seriesModel.coordinateSystem;
const baseAxis = coord.getBaseAxis();
let valueAxis: Axis2D | RadiusAxis | AngleAxis;
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';
valueAxis = coord.getOtherAxis(baseAxis as (AngleAxis | RadiusAxis));
}
const animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null;
const axisAnimationModel = baseAxis.model;
const axis2DModel = (baseAxis as Axis2D).model;
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);
......@@ -163,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);
......@@ -172,6 +196,50 @@ class BarView extends ChartView {
const bgEls: BarView['_backgroundEls'] = [];
const oldBgEls = this._backgroundEls;
let hasDuringForOneData = false;
let getDuring: () => (() => void) = () => {
return null;
};
if (coord.type === 'cartesian2d') {
const oldOrder = (baseAxis.scale as OrdinalScale).getCategorySortInfo();
const orderMap = (idx: number) => {
return data.get(valueAxis.dim, idx) as number;
};
if (realtimeSort) {
// Sort in animation during
const isOrderChanged = this._isDataOrderChanged(data, orderMap, oldOrder);
if (isOrderChanged) {
getDuring = () => {
if (!hasDuringForOneData) {
hasDuringForOneData = true;
return () => {
const orderMap = (idx: number) => {
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);
};
}
else {
return () => null;
}
};
}
}
else if (axisSort) {
// Sort now in the first frame
this._updateSort(data, orderMap, baseAxis as Axis2D, api);
}
}
data.diff(oldData)
.add(function (dataIndex) {
const itemModel = data.getItemModel(dataIndex);
......@@ -204,7 +272,7 @@ class BarView extends ChartView {
}
const el = elementCreator[coord.type](
dataIndex, layout, isHorizontalOrRadial, animationModel, false, roundCap
seriesModel, data, dataIndex, layout, isHorizontalOrRadial, animationModel, false, getDuring(), roundCap
);
data.setItemGraphicEl(dataIndex, el);
group.add(el);
......@@ -250,13 +318,52 @@ class BarView extends ChartView {
if (el) {
clearStates(el);
updateProps(el as Path, {
shape: layout
}, animationModel, newIndex);
if (coord.type === 'cartesian2d'
&& baseAxis.type === 'category' && (baseAxis as Axis2D).model.get('sort')
) {
const rect = layout as RectShape;
let seriesShape, axisShape;
if (baseAxis.dim === 'x') {
axisShape = {
x: rect.x,
width: rect.width
};
seriesShape = {
y: rect.y,
height: rect.height
};
}
else {
axisShape = {
y: rect.y,
height: rect.height
};
seriesShape = {
x: rect.x,
width: rect.width
};
}
if (!isReorder) {
updateProps(el as Path, { shape: seriesShape }, animationModel, newIndex, null, getDuring());
}
updateProps(el as Path, { shape: axisShape }, axisAnimationModel, newIndex, null);
}
else {
updateProps(el as Path, {
shape: layout
}, animationModel, newIndex, null);
}
const defaultTextGetter = (values: ParsedValue | ParsedValue[]) => {
return getDefaultLabel(seriesModel.getData(), newIndex, values);
};
updateLabel(el, data, newIndex, labelModel, seriesModel, animationModel, defaultTextGetter);
}
else {
el = elementCreator[coord.type](
newIndex, layout, isHorizontalOrRadial, animationModel, true, roundCap
seriesModel, data, newIndex, layout, isHorizontalOrRadial, animationModel, true, getDuring(), roundCap
);
}
......@@ -316,7 +423,85 @@ class BarView extends ChartView {
}
}
remove(ecModel?: GlobalModel): void {
_dataSort(
data: List<BarSeriesModel, DefaultDataVisual>,
map: ((idx: number) => number)
): OrdinalSortInfo[] {
type SortValueInfo = {
mappedValue: number,
ordinalNumber: OrdinalNumber,
beforeSortIndex: number
};
const info: SortValueInfo[] = [];
data.each(idx => {
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 zrUtil.map(info, item => {
return {
ordinalNumber: item.ordinalNumber,
beforeSortIndex: item.beforeSortIndex
};
});
}
_isDataOrderChanged(
data: List<BarSeriesModel, DefaultDataVisual>,
orderMap: ((idx: number) => number),
oldOrder: OrdinalSortInfo[]
): boolean {
const oldCount = oldOrder ? oldOrder.length : 0;
if (oldCount !== data.count()) {
return true;
}
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<BarSeriesModel, DefaultDataVisual>,
orderMap: ((idx: number) => number),
baseAxis: Axis2D,
api: ExtensionAPI
) {
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 action = {
type: 'changeAxisOrder',
componentType: baseAxis.dim + 'Axis',
axisId: baseAxis.index
} as Payload;
api.dispatchAction(action);
}
}
remove(ecModel?: GlobalModel) {
this._clear(ecModel);
}
......@@ -399,8 +584,10 @@ const clip: {
interface ElementCreator {
(
dataIndex: number, layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean,
animationModel: BarSeriesModel, isUpdate: boolean, roundCap?: boolean
seriesModel: BarSeriesModel, data: List, newIndex: number,
layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean,
animationModel: BarSeriesModel, isUpdate: boolean, during: () => void,
roundCap?: boolean
): BarPossiblePath
}
......@@ -409,8 +596,8 @@ const elementCreator: {
} = {
cartesian2d(
dataIndex, layout: RectLayout, isHorizontal,
animationModel, isUpdate
seriesModel, data, newIndex, layout: RectLayout, isHorizontal,
animationModel, isUpdate, during
) {
const rect = new Rect({
shape: zrUtil.extend({}, layout),
......@@ -427,17 +614,25 @@ const elementCreator: {
const animateTarget = {} as RectShape;
rectShape[animateProperty] = 0;
animateTarget[animateProperty] = layout[animateProperty];
(isUpdate ? updateProps : initProps)(rect, {
shape: animateTarget
}, animationModel, dataIndex);
}, 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, defaultTextGetter);
}
return rect;
},
polar(
dataIndex: number, layout: SectorLayout, isRadial: boolean,
animationModel, isUpdate, 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
......@@ -462,8 +657,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;
......
......@@ -20,22 +20,31 @@
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 interpolatedValues == null
? 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 = interpolatedValues == null
? retrieveRawValue(data, dataIndex, labelDims[i])
: interpolatedValues;
vals.push(val);
}
return vals.join(' ');
......
......@@ -178,7 +178,7 @@ class Axis {
const ticksCoords = map(ticks, function (tickValue) {
return {
coord: this.dataToCoord(tickValue),
tickValue: tickValue
tickValue: this.scale instanceof OrdinalScale ? this.scale.getCategoryIndex(tickValue) : tickValue
};
}, this);
......
......@@ -18,11 +18,12 @@
*/
import Axis from '../Axis';
import { DimensionName } from '../../util/types';
import { DimensionName, OrdinalSortInfo } from '../../util/types';
import Scale from '../../scale/Scale';
import CartesianAxisModel, { CartesianAxisPosition } from './AxisModel';
import Grid from './Grid';
import { OptionAxisType } from '../axisCommonTypes';
import OrdinalScale from '../../scale/Ordinal';
interface Axis2D {
......@@ -110,6 +111,18 @@ class Axis2D extends Axis {
return this.coordToData(this.toLocalCoord(point[this.dim === 'x' ? 0 : 1]), clamp);
}
/**
* Set ordinalSortInfo
* @param info new OrdinalSortInfo
*/
setCategorySortInfo(info: OrdinalSortInfo[]): boolean {
if (this.type !== 'category') {
return false;
}
this.model.option.categorySortInfo = info;
(this.scale as OrdinalScale).setCategorySortInfo(info);
}
}
......
......@@ -25,6 +25,7 @@ import Axis2D from './Axis2D';
import { AxisBaseOption } from '../axisCommonTypes';
import GridModel from './GridModel';
import { AxisBaseModel } from '../AxisBaseModel';
import {OrdinalSortInfo} from '../../util/types';
export type CartesianAxisPosition = 'top' | 'bottom' | 'left' | 'right';
......@@ -35,6 +36,10 @@ interface CartesianAxisOption extends AxisBaseOption {
position?: CartesianAxisPosition;
// Offset is for multiple axis on the same position.
offset?: number;
sort?: boolean;
realtimeSort?: boolean;
sortSeriesIndex?: number;
categorySortInfo?: OrdinalSortInfo[];
}
class CartesianAxisModel extends ComponentModel<CartesianAxisOption>
......@@ -61,7 +66,11 @@ zrUtil.mixin(CartesianAxisModel, AxisModelCommonMixin);
const extraOption: CartesianAxisOption = {
// gridIndex: 0,
// gridId: '',
offset: 0
offset: 0,
sort: false,
realtimeSort: false,
sortSeriesIndex: null,
categorySortInfo: []
};
axisModelCreator<CartesianAxisOption, typeof CartesianAxisModel>('x', CartesianAxisModel, extraOption);
......
......@@ -47,6 +47,7 @@ import { Dictionary } from 'zrender/src/core/types';
import {CoordinateSystemMaster} from '../CoordinateSystem';
import { ScaleDataValue } from '../../util/types';
import List from '../../data/List';
import OrdinalScale from '../../scale/Ordinal';
import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper';
......@@ -407,10 +408,19 @@ 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 categorySortInfo = axis.model.get('categorySortInfo');
(axis.scale as OrdinalScale).setCategorySortInfo(categorySortInfo);
}
});
ecModel.eachSeries(function (seriesModel) {
if (isCartesian2DSeries(seriesModel)) {
const axesModelMap = findAxisModels(seriesModel);
......
......@@ -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 } from '../../util/types';
import { DataHost, DisplayState, TooltipRenderMode, CallbackDataParams, ColorString, ZRColor, OptionDataValue, ParsedValue } from '../../util/types';
import GlobalModel from '../Global';
import Element from 'zrender/src/Element';
const DIMENSION_LABEL_REG = /\{@(.+?)\}/g;
......@@ -33,6 +34,7 @@ interface DataFormatMixin extends DataHost {
componentIndex: number;
id: string;
name: string;
animatedValue: OptionDataValue[];
}
class DataFormatMixin {
......@@ -43,7 +45,7 @@ class DataFormatMixin {
getDataParams(
dataIndex: number,
dataType?: string,
el?: Element // May be used in override.
el?: Element, // May be used in override.
): CallbackDataParams {
const data = this.getData(dataType);
......@@ -96,13 +98,20 @@ class DataFormatMixin {
status?: DisplayState,
dataType?: string,
dimIndex?: number,
labelProp?: string
labelProp?: string,
// interpolateValues?: ParsedValue | ParsedValue[]
extendParams?: Partial<CallbackDataParams>
): 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);
if (extendParams) {
zrUtil.extend(params, extendParams);
}
if (dimIndex != null && (params.value instanceof Array)) {
params.value = params.value[dimIndex];
}
......
......@@ -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,6 +39,7 @@ class OrdinalScale extends Scale {
readonly type = 'ordinal';
private _ordinalMeta: OrdinalMeta;
private _categorySortInfo: OrdinalSortInfo[];
constructor(setting?: {
......@@ -54,6 +55,7 @@ class OrdinalScale extends Scale {
ordinalMeta = new OrdinalMeta({categories: ordinalMeta});
}
this._ordinalMeta = ordinalMeta;
this._categorySortInfo = [];
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.getCategoryIndex(this.parse(val));
return scaleHelper.normalize(val, this._extent);
}
scale(val: number): OrdinalNumber {
val = this.getCategoryIndex(val);
return Math.round(scaleHelper.scale(val, this._extent));
}
......@@ -99,6 +103,23 @@ class OrdinalScale extends Scale {
return;
}
setCategorySortInfo(info: OrdinalSortInfo[]): void {
this._categorySortInfo = info;
}
getCategorySortInfo(): OrdinalSortInfo[] {
return this._categorySortInfo;
}
getCategoryIndex(n: OrdinalNumber): OrdinalNumber {
if (this._categorySortInfo.length) {
return this._categorySortInfo[n].beforeSortIndex;
}
else {
return n;
}
}
/**
* Get item on rank n
*/
......
......@@ -21,7 +21,7 @@
import * as clazzUtil from '../util/clazz';
import { Dictionary } from 'zrender/src/core/types';
import List from '../data/List';
import { DimensionName, ScaleDataValue, OptionDataValue } from '../util/types';
import { DimensionName, ScaleDataValue, OptionDataValue, DimensionLoose } from '../util/types';
import { ScaleRawExtentInfo } from '../coord/scaleRawExtentInfo';
......@@ -85,7 +85,7 @@ abstract class Scale {
/**
* Set extent from data
*/
unionExtentFromData(data: List, dim: DimensionName): void {
unionExtentFromData(data: List, dim: DimensionName | DimensionLoose): void {
this.unionExtent(data.getApproximateExtent(dim));
}
......
......@@ -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,10 @@ import {
ColorString,
DataModel,
ECEventData,
ZRStyleProps
ZRStyleProps,
SeriesOption,
ParsedValue,
CallbackDataParams
} from './types';
import GlobalModel from '../model/Global';
import { makeInner } from './model';
......@@ -72,6 +75,11 @@ import {
map,
defaults
} from 'zrender/src/core/util';
import * as numberUtil from './number';
import SeriesModel from '../model/Series';
import {OnframeCallback, interpolateNumber} from 'zrender/src/animation/Animator';
import List from '../data/List';
import DataFormatMixin from '../model/mixin/dataFormat';
const mathMax = Math.max;
......@@ -614,7 +622,8 @@ interface SetLabelStyleOpt<LDI> extends TextCommonParams {
state: DisplayState,
dataType: string,
labelDimIndex: number,
labelProp: string
labelProp: string,
extendParams?: Partial<CallbackDataParams>
) => string
},
labelDataIndex?: LDI,
......@@ -623,6 +632,33 @@ interface SetLabelStyleOpt<LDI> extends TextCommonParams {
}
function getLabelText<LDI>(opt?: SetLabelStyleOpt<LDI>, 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, {
value: 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.
......@@ -652,26 +688,6 @@ export function setLabelStyle<LDI>(
// 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();
......@@ -728,8 +744,9 @@ export function setLabelStyle<LDI>(
// 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) {
......@@ -1056,9 +1073,11 @@ function animateOrSetProps<Props>(
getAnimationDelayParams?: (el: Element<Props>, dataIndex: number) => AnimationDelayCallbackParam
},
dataIndex?: number | (() => void),
cb?: () => void
cb?: () => void,
during?: (percent: number) => void
) {
if (typeof dataIndex === 'function') {
during = cb;
cb = dataIndex;
dataIndex = null;
}
......@@ -1095,7 +1114,8 @@ function animateOrSetProps<Props>(
delay: animationDelay || 0,
easing: animationEasing,
done: cb,
force: !!cb
force: !!cb || !!during,
during: during
})
: (el.stopAnimation(), el.attr(props), cb && cb());
}
......@@ -1128,9 +1148,10 @@ function updateProps<Props>(
// TODO: TYPE AnimatableModel
animatableModel?: Model<AnimationOptionMixin>,
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};
......@@ -1148,9 +1169,107 @@ export function initProps<Props>(
props: Props,
animatableModel?: Model<AnimationOptionMixin>,
dataIndex?: number | (() => void),
cb?: () => void
cb?: () => void,
during?: () => void
) {
animateOrSetProps(false, el, props, animatableModel, dataIndex, cb, during);
}
function animateOrSetLabel<Props extends PathProps>(
isUpdate: boolean,
el: Element<Props>,
data: List,
dataIndex: number,
labelModel: Model<LabelOption>,
seriesModel: SeriesModel,
animatableModel?: Model<AnimationOptionMixin>,
defaultTextGetter?: (value: ParsedValue[] | ParsedValue) => string
) {
const element = el as Element<Props> & { __value: ParsedValue[] | ParsedValue };
const valueAnimationEnabled = labelModel && labelModel.get('valueAnimation');
if (valueAnimationEnabled) {
const precisionOption = labelModel.get('precision');
let precision: number = precisionOption === 'auto' ? 0 : precisionOption;
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 during = (percent: number) => {
let interpolated;
if (isRawValueNumber) {
const value = interpolateNumber(0, interpolateValues as number, percent);
interpolated = numberUtil.round(value, 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 {
const value = interpolateNumber(0, (interpolateValues as number[])[i], percent);
interpolated[i] = numberUtil.round(value), precision;
++j;
}
}
}
const text = el.getTextContent();
if (text) {
const labelText = getLabelText({
labelDataIndex: dataIndex,
labelFetcher: seriesModel,
defaultText: defaultTextGetter
? defaultTextGetter(interpolated)
: interpolated + ''
}, interpolated);
text.style.text = labelText.normal;
text.dirty();
}
};
const props: ElementProps = {};
animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, null, during);
}
}
export function updateLabel<Props>(
el: Element<Props>,
data: List,
dataIndex: number,
labelModel: Model<LabelOption>,
seriesModel: SeriesModel,
animatableModel?: Model<AnimationOptionMixin>,
defaultTextGetter?: (value: ParsedValue[] | ParsedValue) => string
) {
animateOrSetLabel(true, el, data, dataIndex, labelModel, seriesModel, animatableModel, defaultTextGetter);
}
export function initLabel<Props>(
el: Element<Props>,
data: List,
dataIndex: number,
labelModel: Model<LabelOption>,
seriesModel: SeriesModel,
animatableModel?: Model<AnimationOptionMixin>,
defaultTextGetter?: (value: ParsedValue[] | ParsedValue) => string
) {
animateOrSetProps(false, el, props, animatableModel, dataIndex, cb);
animateOrSetLabel(false, el, data, dataIndex, labelModel, seriesModel, animatableModel, defaultTextGetter);
}
/**
......
......@@ -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?
......@@ -762,6 +766,8 @@ export interface LabelOption extends TextCommonOption {
overflow?: TextStyleProps['overflow']
silent?: boolean
precision?: number | 'auto'
valueAnimation?: boolean
// TODO: TYPE not all label support formatter
// formatter?: string | ((params: CallbackDataParams) => string)
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册