未验证 提交 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'; ...@@ -24,6 +24,7 @@ import {layout, largeLayout} from '../layout/barGrid';
import '../coord/cartesian/Grid'; import '../coord/cartesian/Grid';
import './bar/BarSeries'; import './bar/BarSeries';
import './bar/BarView'; import './bar/BarView';
import '../action/changeAxisOrder';
// In case developer forget to include grid component // In case developer forget to include grid component
import '../component/gridSimple'; import '../component/gridSimple';
......
...@@ -27,18 +27,21 @@ import { ...@@ -27,18 +27,21 @@ import {
initProps, initProps,
enableHoverEmphasis, enableHoverEmphasis,
setLabelStyle, setLabelStyle,
clearStates clearStates,
updateLabel,
initLabel
} from '../../util/graphic'; } from '../../util/graphic';
import Path, { PathProps } from 'zrender/src/graphic/Path'; import Path, { PathProps } from 'zrender/src/graphic/Path';
import * as numberUtil from '../../util/number';
import Group from 'zrender/src/graphic/Group'; import Group from 'zrender/src/graphic/Group';
import {throttle} from '../../util/throttle'; import {throttle} from '../../util/throttle';
import {createClipPath} from '../helper/createClipPathFromCoordSys'; import {createClipPath} from '../helper/createClipPathFromCoordSys';
import Sausage from '../../util/shape/sausage'; import Sausage from '../../util/shape/sausage';
import ChartView from '../../view/Chart'; import ChartView from '../../view/Chart';
import List from '../../data/List'; import List, {DefaultDataVisual} from '../../data/List';
import GlobalModel from '../../model/Global'; import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../ExtensionAPI'; 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 BarSeriesModel, { BarSeriesOption, BarDataItemOption } from './BarSeries';
import type Axis2D from '../../coord/cartesian/Axis2D'; import type Axis2D from '../../coord/cartesian/Axis2D';
import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D';
...@@ -46,6 +49,9 @@ import type { RectLike } from 'zrender/src/core/BoundingRect'; ...@@ -46,6 +49,9 @@ import type { RectLike } from 'zrender/src/core/BoundingRect';
import type Model from '../../model/Model'; import type Model from '../../model/Model';
import { isCoordinateSystemType } from '../../coord/CoordinateSystem'; import { isCoordinateSystemType } from '../../coord/CoordinateSystem';
import { getDefaultLabel } from '../helper/labelHelper'; 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_WIDTH_QUERY = ['itemStyle', 'borderWidth'] as const;
const BAR_BORDER_RADIUS_QUERY = ['itemStyle', 'borderRadius'] as const; const BAR_BORDER_RADIUS_QUERY = ['itemStyle', 'borderRadius'] as const;
...@@ -100,17 +106,18 @@ class BarView extends ChartView { ...@@ -100,17 +106,18 @@ class BarView extends ChartView {
private _backgroundEls: (Rect | Sector)[]; private _backgroundEls: (Rect | Sector)[];
render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI): void { render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) {
this._updateDrawMode(seriesModel); this._updateDrawMode(seriesModel);
const coordinateSystemType = seriesModel.get('coordinateSystem'); const coordinateSystemType = seriesModel.get('coordinateSystem');
const isReorder = payload && payload.type === 'changeAxisOrder';
if (coordinateSystemType === 'cartesian2d' if (coordinateSystemType === 'cartesian2d'
|| coordinateSystemType === 'polar' || coordinateSystemType === 'polar'
) { ) {
this._isLargeDraw this._isLargeDraw
? this._renderLarge(seriesModel, ecModel, api) ? this._renderLarge(seriesModel, ecModel, api)
: this._renderNormal(seriesModel, ecModel, api); : this._renderNormal(seriesModel, ecModel, api, isReorder);
} }
else if (__DEV__) { else if (__DEV__) {
console.warn('Only cartesian2d and polar supported for bar.'); console.warn('Only cartesian2d and polar supported for bar.');
...@@ -138,23 +145,38 @@ class BarView extends ChartView { ...@@ -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 group = this.group;
const data = seriesModel.getData(); const data = seriesModel.getData();
const oldData = this._data; const oldData = this._data;
const coord = seriesModel.coordinateSystem; const coord = seriesModel.coordinateSystem;
const baseAxis = coord.getBaseAxis(); const baseAxis = coord.getBaseAxis();
let valueAxis: Axis2D | RadiusAxis | AngleAxis;
let isHorizontalOrRadial: boolean; let isHorizontalOrRadial: boolean;
if (coord.type === 'cartesian2d') { if (coord.type === 'cartesian2d') {
isHorizontalOrRadial = (baseAxis as Axis2D).isHorizontal(); isHorizontalOrRadial = (baseAxis as Axis2D).isHorizontal();
valueAxis = coord.getOtherAxis(baseAxis as Axis2D);
} }
else if (coord.type === 'polar') { else if (coord.type === 'polar') {
isHorizontalOrRadial = baseAxis.dim === 'angle'; isHorizontalOrRadial = baseAxis.dim === 'angle';
valueAxis = coord.getOtherAxis(baseAxis as (AngleAxis | RadiusAxis));
} }
const animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null; 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 needsClip = seriesModel.get('clip', true);
const coordSysClipArea = getClipArea(coord, data); const coordSysClipArea = getClipArea(coord, data);
...@@ -163,6 +185,8 @@ class BarView extends ChartView { ...@@ -163,6 +185,8 @@ class BarView extends ChartView {
// We don't use clipPath in normal mode because we needs a perfect animation // We don't use clipPath in normal mode because we needs a perfect animation
// And don't want the label are clipped. // And don't want the label are clipped.
const labelModel = seriesModel.getModel('label');
const roundCap = seriesModel.get('roundCap', true); const roundCap = seriesModel.get('roundCap', true);
const drawBackground = seriesModel.get('showBackground', true); const drawBackground = seriesModel.get('showBackground', true);
...@@ -172,6 +196,50 @@ class BarView extends ChartView { ...@@ -172,6 +196,50 @@ class BarView extends ChartView {
const bgEls: BarView['_backgroundEls'] = []; const bgEls: BarView['_backgroundEls'] = [];
const oldBgEls = this._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) data.diff(oldData)
.add(function (dataIndex) { .add(function (dataIndex) {
const itemModel = data.getItemModel(dataIndex); const itemModel = data.getItemModel(dataIndex);
...@@ -204,7 +272,7 @@ class BarView extends ChartView { ...@@ -204,7 +272,7 @@ class BarView extends ChartView {
} }
const el = elementCreator[coord.type]( const el = elementCreator[coord.type](
dataIndex, layout, isHorizontalOrRadial, animationModel, false, roundCap seriesModel, data, dataIndex, layout, isHorizontalOrRadial, animationModel, false, getDuring(), roundCap
); );
data.setItemGraphicEl(dataIndex, el); data.setItemGraphicEl(dataIndex, el);
group.add(el); group.add(el);
...@@ -250,13 +318,52 @@ class BarView extends ChartView { ...@@ -250,13 +318,52 @@ class BarView extends ChartView {
if (el) { if (el) {
clearStates(el); clearStates(el);
updateProps(el as Path, {
shape: layout if (coord.type === 'cartesian2d'
}, animationModel, newIndex); && 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 { else {
el = elementCreator[coord.type]( 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 { ...@@ -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); this._clear(ecModel);
} }
...@@ -399,8 +584,10 @@ const clip: { ...@@ -399,8 +584,10 @@ const clip: {
interface ElementCreator { interface ElementCreator {
( (
dataIndex: number, layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean, seriesModel: BarSeriesModel, data: List, newIndex: number,
animationModel: BarSeriesModel, isUpdate: boolean, roundCap?: boolean layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean,
animationModel: BarSeriesModel, isUpdate: boolean, during: () => void,
roundCap?: boolean
): BarPossiblePath ): BarPossiblePath
} }
...@@ -409,8 +596,8 @@ const elementCreator: { ...@@ -409,8 +596,8 @@ const elementCreator: {
} = { } = {
cartesian2d( cartesian2d(
dataIndex, layout: RectLayout, isHorizontal, seriesModel, data, newIndex, layout: RectLayout, isHorizontal,
animationModel, isUpdate animationModel, isUpdate, during
) { ) {
const rect = new Rect({ const rect = new Rect({
shape: zrUtil.extend({}, layout), shape: zrUtil.extend({}, layout),
...@@ -427,17 +614,25 @@ const elementCreator: { ...@@ -427,17 +614,25 @@ const elementCreator: {
const animateTarget = {} as RectShape; const animateTarget = {} as RectShape;
rectShape[animateProperty] = 0; rectShape[animateProperty] = 0;
animateTarget[animateProperty] = layout[animateProperty]; animateTarget[animateProperty] = layout[animateProperty];
(isUpdate ? updateProps : initProps)(rect, { (isUpdate ? updateProps : initProps)(rect, {
shape: animateTarget 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; return rect;
}, },
polar( polar(
dataIndex: number, layout: SectorLayout, isRadial: boolean, seriesModel, data, newIndex, layout: SectorLayout, isRadial: boolean,
animationModel, isUpdate, roundCap animationModel, isUpdate, during, roundCap
) { ) {
// Keep the same logic with bar in catesion: use end value to control // Keep the same logic with bar in catesion: use end value to control
// direction. Notice that if clockwise is true (by default), the sector // direction. Notice that if clockwise is true (by default), the sector
...@@ -462,8 +657,9 @@ const elementCreator: { ...@@ -462,8 +657,9 @@ const elementCreator: {
sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle; sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle;
animateTarget[animateProperty] = layout[animateProperty]; animateTarget[animateProperty] = layout[animateProperty];
(isUpdate ? updateProps : initProps)(sector, { (isUpdate ? updateProps : initProps)(sector, {
shape: animateTarget shape: animateTarget,
}, animationModel, dataIndex); // __value: typeof dataValue === 'string' ? parseInt(dataValue, 10) : dataValue
}, animationModel);
} }
return sector; return sector;
......
...@@ -20,22 +20,31 @@ ...@@ -20,22 +20,31 @@
import {retrieveRawValue} from '../../data/helper/dataProvider'; import {retrieveRawValue} from '../../data/helper/dataProvider';
import List from '../../data/List'; import List from '../../data/List';
import {ParsedValue} from '../../util/types';
/** /**
* @return label string. Not null/undefined * @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 labelDims = data.mapDimensionsAll('defaultedLabel');
const len = labelDims.length; const len = labelDims.length;
// Simple optimization (in lots of cases, label dims length is 1) // Simple optimization (in lots of cases, label dims length is 1)
if (len === 1) { if (len === 1) {
return retrieveRawValue(data, dataIndex, labelDims[0]); return interpolatedValues == null
? retrieveRawValue(data, dataIndex, labelDims[0])
: interpolatedValues;
} }
else if (len) { else if (len) {
const vals = []; const vals = [];
for (let i = 0; i < labelDims.length; i++) { 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); vals.push(val);
} }
return vals.join(' '); return vals.join(' ');
......
...@@ -178,7 +178,7 @@ class Axis { ...@@ -178,7 +178,7 @@ class Axis {
const ticksCoords = map(ticks, function (tickValue) { const ticksCoords = map(ticks, function (tickValue) {
return { return {
coord: this.dataToCoord(tickValue), coord: this.dataToCoord(tickValue),
tickValue: tickValue tickValue: this.scale instanceof OrdinalScale ? this.scale.getCategoryIndex(tickValue) : tickValue
}; };
}, this); }, this);
......
...@@ -18,11 +18,12 @@ ...@@ -18,11 +18,12 @@
*/ */
import Axis from '../Axis'; import Axis from '../Axis';
import { DimensionName } from '../../util/types'; import { DimensionName, OrdinalSortInfo } from '../../util/types';
import Scale from '../../scale/Scale'; import Scale from '../../scale/Scale';
import CartesianAxisModel, { CartesianAxisPosition } from './AxisModel'; import CartesianAxisModel, { CartesianAxisPosition } from './AxisModel';
import Grid from './Grid'; import Grid from './Grid';
import { OptionAxisType } from '../axisCommonTypes'; import { OptionAxisType } from '../axisCommonTypes';
import OrdinalScale from '../../scale/Ordinal';
interface Axis2D { interface Axis2D {
...@@ -110,6 +111,18 @@ class Axis2D extends Axis { ...@@ -110,6 +111,18 @@ class Axis2D extends Axis {
return this.coordToData(this.toLocalCoord(point[this.dim === 'x' ? 0 : 1]), clamp); 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'; ...@@ -25,6 +25,7 @@ import Axis2D from './Axis2D';
import { AxisBaseOption } from '../axisCommonTypes'; import { AxisBaseOption } from '../axisCommonTypes';
import GridModel from './GridModel'; import GridModel from './GridModel';
import { AxisBaseModel } from '../AxisBaseModel'; import { AxisBaseModel } from '../AxisBaseModel';
import {OrdinalSortInfo} from '../../util/types';
export type CartesianAxisPosition = 'top' | 'bottom' | 'left' | 'right'; export type CartesianAxisPosition = 'top' | 'bottom' | 'left' | 'right';
...@@ -35,6 +36,10 @@ interface CartesianAxisOption extends AxisBaseOption { ...@@ -35,6 +36,10 @@ interface CartesianAxisOption extends AxisBaseOption {
position?: CartesianAxisPosition; position?: CartesianAxisPosition;
// Offset is for multiple axis on the same position. // Offset is for multiple axis on the same position.
offset?: number; offset?: number;
sort?: boolean;
realtimeSort?: boolean;
sortSeriesIndex?: number;
categorySortInfo?: OrdinalSortInfo[];
} }
class CartesianAxisModel extends ComponentModel<CartesianAxisOption> class CartesianAxisModel extends ComponentModel<CartesianAxisOption>
...@@ -61,7 +66,11 @@ zrUtil.mixin(CartesianAxisModel, AxisModelCommonMixin); ...@@ -61,7 +66,11 @@ zrUtil.mixin(CartesianAxisModel, AxisModelCommonMixin);
const extraOption: CartesianAxisOption = { const extraOption: CartesianAxisOption = {
// gridIndex: 0, // gridIndex: 0,
// gridId: '', // gridId: '',
offset: 0 offset: 0,
sort: false,
realtimeSort: false,
sortSeriesIndex: null,
categorySortInfo: []
}; };
axisModelCreator<CartesianAxisOption, typeof CartesianAxisModel>('x', CartesianAxisModel, extraOption); axisModelCreator<CartesianAxisOption, typeof CartesianAxisModel>('x', CartesianAxisModel, extraOption);
......
...@@ -47,6 +47,7 @@ import { Dictionary } from 'zrender/src/core/types'; ...@@ -47,6 +47,7 @@ import { Dictionary } from 'zrender/src/core/types';
import {CoordinateSystemMaster} from '../CoordinateSystem'; import {CoordinateSystemMaster} from '../CoordinateSystem';
import { ScaleDataValue } from '../../util/types'; import { ScaleDataValue } from '../../util/types';
import List from '../../data/List'; import List from '../../data/List';
import OrdinalScale from '../../scale/Ordinal';
import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper'; import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper';
...@@ -407,10 +408,19 @@ class Grid implements CoordinateSystemMaster { ...@@ -407,10 +408,19 @@ class Grid implements CoordinateSystemMaster {
* Update cartesian properties from series. * Update cartesian properties from series.
*/ */
private _updateScale(ecModel: GlobalModel, gridModel: GridModel): void { private _updateScale(ecModel: GlobalModel, gridModel: GridModel): void {
const sortedDataValue: number[] = [];
const sortedDataIndex: number[] = [];
let hasCategoryIndices = false;
// Reset scale // Reset scale
each(this._axesList, function (axis) { each(this._axesList, function (axis) {
axis.scale.setExtent(Infinity, -Infinity); 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) { ecModel.eachSeries(function (seriesModel) {
if (isCartesian2DSeries(seriesModel)) { if (isCartesian2DSeries(seriesModel)) {
const axesModelMap = findAxisModels(seriesModel); const axesModelMap = findAxisModels(seriesModel);
......
...@@ -17,11 +17,12 @@ ...@@ -17,11 +17,12 @@
* under the License. * under the License.
*/ */
import * as zrUtil from 'zrender/src/core/util';
import Element from 'zrender/src/Element';
import {retrieveRawValue} from '../../data/helper/dataProvider'; import {retrieveRawValue} from '../../data/helper/dataProvider';
import {formatTpl} from '../../util/format'; 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 GlobalModel from '../Global';
import Element from 'zrender/src/Element';
const DIMENSION_LABEL_REG = /\{@(.+?)\}/g; const DIMENSION_LABEL_REG = /\{@(.+?)\}/g;
...@@ -33,6 +34,7 @@ interface DataFormatMixin extends DataHost { ...@@ -33,6 +34,7 @@ interface DataFormatMixin extends DataHost {
componentIndex: number; componentIndex: number;
id: string; id: string;
name: string; name: string;
animatedValue: OptionDataValue[];
} }
class DataFormatMixin { class DataFormatMixin {
...@@ -43,7 +45,7 @@ class DataFormatMixin { ...@@ -43,7 +45,7 @@ class DataFormatMixin {
getDataParams( getDataParams(
dataIndex: number, dataIndex: number,
dataType?: string, dataType?: string,
el?: Element // May be used in override. el?: Element, // May be used in override.
): CallbackDataParams { ): CallbackDataParams {
const data = this.getData(dataType); const data = this.getData(dataType);
...@@ -96,13 +98,20 @@ class DataFormatMixin { ...@@ -96,13 +98,20 @@ class DataFormatMixin {
status?: DisplayState, status?: DisplayState,
dataType?: string, dataType?: string,
dimIndex?: number, dimIndex?: number,
labelProp?: string labelProp?: string,
// interpolateValues?: ParsedValue | ParsedValue[]
extendParams?: Partial<CallbackDataParams>
): string { ): string {
status = status || 'normal'; status = status || 'normal';
const data = this.getData(dataType); const data = this.getData(dataType);
const itemModel = data.getItemModel(dataIndex); 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)) { if (dimIndex != null && (params.value instanceof Array)) {
params.value = params.value[dimIndex]; params.value = params.value[dimIndex];
} }
......
...@@ -28,7 +28,7 @@ import Scale from './Scale'; ...@@ -28,7 +28,7 @@ import Scale from './Scale';
import OrdinalMeta from '../data/OrdinalMeta'; import OrdinalMeta from '../data/OrdinalMeta';
import List from '../data/List'; import List from '../data/List';
import * as scaleHelper from './helper'; 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 { AxisBaseOption } from '../coord/axisCommonTypes';
import { isArray } from 'zrender/src/core/util'; import { isArray } from 'zrender/src/core/util';
...@@ -39,6 +39,7 @@ class OrdinalScale extends Scale { ...@@ -39,6 +39,7 @@ class OrdinalScale extends Scale {
readonly type = 'ordinal'; readonly type = 'ordinal';
private _ordinalMeta: OrdinalMeta; private _ordinalMeta: OrdinalMeta;
private _categorySortInfo: OrdinalSortInfo[];
constructor(setting?: { constructor(setting?: {
...@@ -54,6 +55,7 @@ class OrdinalScale extends Scale { ...@@ -54,6 +55,7 @@ class OrdinalScale extends Scale {
ordinalMeta = new OrdinalMeta({categories: ordinalMeta}); ordinalMeta = new OrdinalMeta({categories: ordinalMeta});
} }
this._ordinalMeta = ordinalMeta; this._ordinalMeta = ordinalMeta;
this._categorySortInfo = [];
this._extent = this.getSetting('extent') || [0, ordinalMeta.categories.length - 1]; this._extent = this.getSetting('extent') || [0, ordinalMeta.categories.length - 1];
} }
...@@ -74,10 +76,12 @@ class OrdinalScale extends Scale { ...@@ -74,10 +76,12 @@ class OrdinalScale extends Scale {
* Normalize given rank or name to linear [0, 1] * Normalize given rank or name to linear [0, 1]
*/ */
normalize(val: OrdinalRawValue | OrdinalNumber): number { 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 { scale(val: number): OrdinalNumber {
val = this.getCategoryIndex(val);
return Math.round(scaleHelper.scale(val, this._extent)); return Math.round(scaleHelper.scale(val, this._extent));
} }
...@@ -99,6 +103,23 @@ class OrdinalScale extends Scale { ...@@ -99,6 +103,23 @@ class OrdinalScale extends Scale {
return; 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 * Get item on rank n
*/ */
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
import * as clazzUtil from '../util/clazz'; import * as clazzUtil from '../util/clazz';
import { Dictionary } from 'zrender/src/core/types'; import { Dictionary } from 'zrender/src/core/types';
import List from '../data/List'; 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'; import { ScaleRawExtentInfo } from '../coord/scaleRawExtentInfo';
...@@ -85,7 +85,7 @@ abstract class Scale { ...@@ -85,7 +85,7 @@ abstract class Scale {
/** /**
* Set extent from data * Set extent from data
*/ */
unionExtentFromData(data: List, dim: DimensionName): void { unionExtentFromData(data: List, dim: DimensionName | DimensionLoose): void {
this.unionExtent(data.getApproximateExtent(dim)); this.unionExtent(data.getApproximateExtent(dim));
} }
......
...@@ -46,7 +46,7 @@ import LRU from 'zrender/src/core/LRU'; ...@@ -46,7 +46,7 @@ import LRU from 'zrender/src/core/LRU';
import Displayable, { DisplayableProps } from 'zrender/src/graphic/Displayable'; import Displayable, { DisplayableProps } from 'zrender/src/graphic/Displayable';
import { PatternObject } from 'zrender/src/graphic/Pattern'; import { PatternObject } from 'zrender/src/graphic/Pattern';
import { GradientObject } from 'zrender/src/graphic/Gradient'; 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 Model from '../model/Model';
import { import {
AnimationOptionMixin, AnimationOptionMixin,
...@@ -58,7 +58,10 @@ import { ...@@ -58,7 +58,10 @@ import {
ColorString, ColorString,
DataModel, DataModel,
ECEventData, ECEventData,
ZRStyleProps ZRStyleProps,
SeriesOption,
ParsedValue,
CallbackDataParams
} from './types'; } from './types';
import GlobalModel from '../model/Global'; import GlobalModel from '../model/Global';
import { makeInner } from './model'; import { makeInner } from './model';
...@@ -72,6 +75,11 @@ import { ...@@ -72,6 +75,11 @@ import {
map, map,
defaults defaults
} from 'zrender/src/core/util'; } 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; const mathMax = Math.max;
...@@ -614,7 +622,8 @@ interface SetLabelStyleOpt<LDI> extends TextCommonParams { ...@@ -614,7 +622,8 @@ interface SetLabelStyleOpt<LDI> extends TextCommonParams {
state: DisplayState, state: DisplayState,
dataType: string, dataType: string,
labelDimIndex: number, labelDimIndex: number,
labelProp: string labelProp: string,
extendParams?: Partial<CallbackDataParams>
) => string ) => string
}, },
labelDataIndex?: LDI, labelDataIndex?: LDI,
...@@ -623,6 +632,33 @@ interface SetLabelStyleOpt<LDI> extends TextCommonParams { ...@@ -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 * Set normal styles and emphasis styles about text on target element
* If target is a ZRText. It will create a new style object. * If target is a ZRText. It will create a new style object.
...@@ -652,26 +688,6 @@ export function setLabelStyle<LDI>( ...@@ -652,26 +688,6 @@ export function setLabelStyle<LDI>(
// label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`. // label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`.
let richText = isSetOnText ? targetEl as ZRText : null; let richText = isSetOnText ? targetEl as ZRText : null;
if (showNormal || showEmphasis) { 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) { if (!isSetOnText) {
// Reuse the previous // Reuse the previous
richText = targetEl.getTextContent(); richText = targetEl.getTextContent();
...@@ -728,8 +744,9 @@ export function setLabelStyle<LDI>( ...@@ -728,8 +744,9 @@ export function setLabelStyle<LDI>(
// auto slient is those cases. // auto slient is those cases.
richText.silent = !!normalModel.getShallow('silent'); richText.silent = !!normalModel.getShallow('silent');
normalStyle.text = normalStyleText; const labelText = getLabelText(opt);
emphasisState.style.text = emphasisStyleText; normalStyle.text = labelText.normal;
emphasisState.style.text = labelText.emphasis;
// Keep x and y // Keep x and y
if (richText.style.x != null) { if (richText.style.x != null) {
...@@ -1056,9 +1073,11 @@ function animateOrSetProps<Props>( ...@@ -1056,9 +1073,11 @@ function animateOrSetProps<Props>(
getAnimationDelayParams?: (el: Element<Props>, dataIndex: number) => AnimationDelayCallbackParam getAnimationDelayParams?: (el: Element<Props>, dataIndex: number) => AnimationDelayCallbackParam
}, },
dataIndex?: number | (() => void), dataIndex?: number | (() => void),
cb?: () => void cb?: () => void,
during?: (percent: number) => void
) { ) {
if (typeof dataIndex === 'function') { if (typeof dataIndex === 'function') {
during = cb;
cb = dataIndex; cb = dataIndex;
dataIndex = null; dataIndex = null;
} }
...@@ -1095,7 +1114,8 @@ function animateOrSetProps<Props>( ...@@ -1095,7 +1114,8 @@ function animateOrSetProps<Props>(
delay: animationDelay || 0, delay: animationDelay || 0,
easing: animationEasing, easing: animationEasing,
done: cb, done: cb,
force: !!cb force: !!cb || !!during,
during: during
}) })
: (el.stopAnimation(), el.attr(props), cb && cb()); : (el.stopAnimation(), el.attr(props), cb && cb());
} }
...@@ -1128,9 +1148,10 @@ function updateProps<Props>( ...@@ -1128,9 +1148,10 @@ function updateProps<Props>(
// TODO: TYPE AnimatableModel // TODO: TYPE AnimatableModel
animatableModel?: Model<AnimationOptionMixin>, animatableModel?: Model<AnimationOptionMixin>,
dataIndex?: number | (() => void), 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}; export {updateProps};
...@@ -1148,9 +1169,107 @@ export function initProps<Props>( ...@@ -1148,9 +1169,107 @@ export function initProps<Props>(
props: Props, props: Props,
animatableModel?: Model<AnimationOptionMixin>, animatableModel?: Model<AnimationOptionMixin>,
dataIndex?: number | (() => void), 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'; ...@@ -242,6 +242,10 @@ export type TooltipRenderMode = 'html' | 'richText';
// Check `convertDataValue` for more details. // Check `convertDataValue` for more details.
export type OrdinalRawValue = string | number; export type OrdinalRawValue = string | number;
export type OrdinalNumber = number; // The number mapped from each OrdinalRawValue. export type OrdinalNumber = number; // The number mapped from each OrdinalRawValue.
export type OrdinalSortInfo = {
ordinalNumber: OrdinalNumber,
beforeSortIndex: number
};
export type ParsedValueNumeric = number | OrdinalNumber; export type ParsedValueNumeric = number | OrdinalNumber;
export type ParsedValue = ParsedValueNumeric | OrdinalRawValue; export type ParsedValue = ParsedValueNumeric | OrdinalRawValue;
// FIXME:TS better name? // FIXME:TS better name?
...@@ -762,6 +766,8 @@ export interface LabelOption extends TextCommonOption { ...@@ -762,6 +766,8 @@ export interface LabelOption extends TextCommonOption {
overflow?: TextStyleProps['overflow'] overflow?: TextStyleProps['overflow']
silent?: boolean silent?: boolean
precision?: number | 'auto'
valueAnimation?: boolean
// TODO: TYPE not all label support formatter // TODO: TYPE not all label support formatter
// formatter?: string | ((params: CallbackDataParams) => string) // formatter?: string | ((params: CallbackDataParams) => string)
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册