From d0e5f43998996909fcc6d4c0ddc7c886b2796386 Mon Sep 17 00:00:00 2001 From: 100pah Date: Wed, 29 Apr 2020 01:57:05 +0800 Subject: [PATCH 01/14] feat: custom series compat. --- src/chart/custom.ts | 1252 +++++++++++++++++------ src/coord/CoordinateSystem.ts | 3 + src/echarts.ts | 4 + src/model/Component.ts | 5 + src/model/mixin/itemStyle.ts | 2 +- src/util/graphic.ts | 39 +- src/util/styleCompat.ts | 256 +++++ src/util/types.ts | 2 + test/circle-packing-with-d3.compat.html | 170 +++ test/circle-packing-with-d3.html | 50 +- test/custom-feature.html | 1 + test/custom-text-content.html | 1193 +++++++++++++++++++++ test/hoverStyle.html | 3 +- 13 files changed, 2658 insertions(+), 322 deletions(-) create mode 100644 src/util/styleCompat.ts create mode 100644 test/circle-packing-with-d3.compat.html create mode 100644 test/custom-text-content.html diff --git a/src/chart/custom.ts b/src/chart/custom.ts index 3b801ff588..3ed15b903b 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -17,7 +17,6 @@ * under the License. */ -// @ts-nocheck import {__DEV__} from '../config'; import * as zrUtil from 'zrender/src/core/util'; @@ -30,24 +29,261 @@ import SeriesModel from '../model/Series'; import Model from '../model/Model'; import ChartView from '../view/Chart'; import {createClipPath} from './helper/createClipPathFromCoordSys'; -import {EventQueryItem, ECEvent} from '../util/types'; -import Element from 'zrender/src/Element'; - +import { + EventQueryItem, ECEvent, SeriesOption, SeriesOnCartesianOptionMixin, + SeriesOnPolarOptionMixin, SeriesOnSingleOptionMixin, SeriesOnGeoOptionMixin, + SeriesOnCalendarOptionMixin, ItemStyleOption, SeriesEncodeOptionMixin, + SeriesTooltipOption, + DimensionLoose, + ParsedValue, + Dictionary, + CallbackDataParams, + Payload, + StageHandlerProgressParams, + LabelOption, + ViewRootGroup, + OptionDataValue, + ZRStyleProps, + DisplayState, + ECElement, + DisplayStateNonNormal +} from '../util/types'; +import Element, { ElementProps, ElementTextConfig } from 'zrender/src/Element'; import prepareCartesian2d from '../coord/cartesian/prepareCustom'; import prepareGeo from '../coord/geo/prepareCustom'; import prepareSingleAxis from '../coord/single/prepareCustom'; import preparePolar from '../coord/polar/prepareCustom'; import prepareCalendar from '../coord/calendar/prepareCustom'; +import ComponentModel from '../model/Component'; +import List, { DefaultDataVisual } from '../data/List'; +import GlobalModel from '../model/Global'; +import { makeInner } from '../util/model'; +import ExtensionAPI from '../ExtensionAPI'; +import Displayable from 'zrender/src/graphic/Displayable'; +import Axis2D from '../coord/cartesian/Axis2D'; +import { RectLike } from 'zrender/src/core/BoundingRect'; +import { PathProps } from 'zrender/src/graphic/Path'; +import { ImageStyleProps } from 'zrender/src/graphic/Image'; +import { CoordinateSystem } from '../coord/CoordinateSystem'; +import { TextStyleProps } from 'zrender/src/graphic/Text'; +import { + convertToEC4StyleForCustomSerise, + isEC4CompatibleStyle, + convertFromEC4CompatibleStyle, + LegacyStyleProps, + warnDeprecated +} from '../util/styleCompat'; +import Transformable from 'zrender/src/core/Transformable'; +import { ItemStyleProps } from '../model/mixin/itemStyle'; + + +const inner = makeInner<{ + info: CustomExtraElementInfo; + customPathData: string; + customGraphicType: string; + customImagePath: CustomImageOption['style']['image']; + customText: string; + txConZ2Set: number; +}, Element>(); + +type CustomExtraElementInfo = Dictionary; +type TransformPropsX = 'x' | 'scaleX' | 'originX'; +type TransformPropsY = 'y' | 'scaleY' | 'originY'; +type TransformProps = TransformPropsX | TransformPropsY | 'rotation'; + + +interface CustomBaseElementOption extends Partial> { + // element type, mandatory. + type: string; + id?: string; + // For animation diff. + name?: string; + info?: CustomExtraElementInfo; + // `false` means remove the textContent. + textContent?: CustomTextOption | false; +}; +interface CustomDisplayableOption extends CustomBaseElementOption, Partial> { + style?: ZRStyleProps; + // `false` means remove emphasis trigger. + styleEmphasis?: ZRStyleProps | false; + emphasis?: CustomDisplayableOptionOnState; +} +interface CustomDisplayableOptionOnState extends Partial> { + // `false` means remove emphasis trigger. + style?: ZRStyleProps | false; +} +interface CustomGroupOption extends CustomBaseElementOption { + type: 'group'; + width?: number; + height?: number; + diffChildrenByName?: boolean; + children: CustomElementOption[]; + $mergeChildren: false | 'byName' | 'byIndex'; +} +interface CustomZRPathOption extends CustomDisplayableOption, Pick { +} +interface CustomSVGPathOption extends CustomDisplayableOption { + type: 'path'; + shape?: { + // SVG Path, like 'M0,0 L0,-20 L70,-1 L70,0 Z' + pathData?: string; + // "d" is the alias of `pathData` follows the SVG convention. + d?: string; + layout?: 'center' | 'cover'; + x?: number; + y?: number; + width?: number; + height?: number; + }; +} +interface CustomImageOption extends CustomDisplayableOption { + type: 'image'; + style?: ImageStyleProps; + emphasis?: CustomImageOptionOnState; +} +interface CustomImageOptionOnState extends CustomDisplayableOptionOnState { + style?: ImageStyleProps; +} +interface CustomTextOption extends CustomDisplayableOption { + type: 'text'; +} +type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImageOption | CustomTextOption; +type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState; +type StyleOption = ZRStyleProps | ImageStyleProps | false; + + +interface CustomSeriesRenderItemAPI extends + CustomSeriesRenderItemCoordinateSystemAPI, + Pick { + value(dim: DimensionLoose, dataIndexInside?: number): ParsedValue; + style(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps; + styleEmphasis(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps; + visual(visualType: string, dataIndexInside?: number): ReturnType; + barLayout(opt: Omit[0], 'axis'>): ReturnType; + currentSeriesIndices(): ReturnType; + font(opt: Parameters[0]): ReturnType; +} +interface CustomSeriesRenderItemParamsCoordSys { + type: string; + // And extra params for each coordinate systems. +} +interface CustomSeriesRenderItemCoordinateSystemAPI { + coord( + data: OptionDataValue | OptionDataValue[], + clamp?: boolean + ): number[]; + size?( + dataSize: OptionDataValue | OptionDataValue[], + dataItem: OptionDataValue | OptionDataValue[] + ): number | number[]; +} +interface CustomSeriesRenderItemParams { + context: {}; + seriesId: string; + seriesName: string; + seriesIndex: number; + coordSys: CustomSeriesRenderItemParamsCoordSys; + dataInsideLength: number; + encode: ReturnType +} +type CustomSeriesRenderItem = ( + params: CustomSeriesRenderItemParams, + api: CustomSeriesRenderItemAPI +) => CustomElementOption; + + +interface CustomSeriesOption extends + SeriesOption, + SeriesEncodeOptionMixin, + SeriesOnCartesianOptionMixin, + SeriesOnPolarOptionMixin, + SeriesOnSingleOptionMixin, + SeriesOnGeoOptionMixin, + SeriesOnCalendarOptionMixin { + + // If set as 'none', do not depends on coord sys. + coordinateSystem?: string | 'none'; + + renderItem?: CustomSeriesRenderItem; + + // Only works on polar and cartesian2d coordinate system. + clip?: boolean; -const CACHED_LABEL_STYLE_PROPERTIES = graphicUtil.CACHED_LABEL_STYLE_PROPERTIES; -const ITEM_STYLE_NORMAL_PATH = ['itemStyle']; -const ITEM_STYLE_EMPHASIS_PATH = ['emphasis', 'itemStyle']; -const LABEL_NORMAL = ['label']; -const LABEL_EMPHASIS = ['emphasis', 'label']; + // FIXME needed? + tooltip?: SeriesTooltipOption; + + itemStyle?: ItemStyleOption; + label?: LabelOption; + emphasis?: { + itemStyle?: ItemStyleOption; + label?: LabelOption; + }; +} + +// Also compat with ec4, where +// `visual('color') visual('borderColor')` is supported. +const STYLE_VISUAL_TYPE = { + color: 'fill', + borderColor: 'stroke' +} as const; + +const VISUAL_PROPS = { + symbol: 1, + symbolSize: 1, + symbolKeepAspect: 1, + legendSymbol: 1, + visualMeta: 1, + liftZ: 1 +} as const; + +const EMPHASIS = 'emphasis' as const; +const NORMAL = 'normal' as const; +const PATH_ITEM_STYLE = { + normal: ['itemStyle'], + emphasis: [EMPHASIS, 'itemStyle'] +} as const; +const PATH_LABEL = { + normal: ['label'], + emphasis: [EMPHASIS, 'label'] +} as const; // Use prefix to avoid index to be the same as el.name, -// which will cause weird udpate animation. +// which will cause weird update animation. const GROUP_DIFF_PREFIX = 'e\0\0'; +type AttachedTxInfo = { + isLegacy: boolean; + normal: { + cfg: ElementTextConfig; + conOpt: CustomElementOption | false; + }; + emphasis: { + cfg: ElementTextConfig; + conOpt: CustomElementOptionOnState; + }; +}; +const attachedTxInfoTmp = { + normal: {}, + emphasis: {} +} as AttachedTxInfo; + +const Z2_SPECIFIED_BIT = { + normal: 0, + emphasis: 1 +} as const; + + + + +export type PrepareCustomInfo = (coordSys: CoordinateSystem) => { + coordSys: CustomSeriesRenderItemParamsCoordSys; + api: CustomSeriesRenderItemCoordinateSystemAPI +}; /** * To reduce total package size of each coordinate systems, the modules `prepareCustom` @@ -60,7 +296,7 @@ const GROUP_DIFF_PREFIX = 'e\0\0'; * size: function (dataSize, dataItem) {} // return size of each axis in coordSys. * }} */ -const prepareCustoms = { +const prepareCustoms: Dictionary = { cartesian2d: prepareCartesian2d, geo: prepareGeo, singleAxis: prepareSingleAxis, @@ -68,29 +304,27 @@ const prepareCustoms = { calendar: prepareCalendar }; +class CustomSeriesModel extends SeriesModel { -// ------ -// Model -// ------ + static type = 'series.custom'; + readonly type = CustomSeriesModel.type; -SeriesModel.extend({ + static dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar']; - type: 'series.custom', + preventAutoZ = true; - dependencies: ['grid', 'polar', 'geo', 'singleAxis', 'calendar'], + currentZLevel: number; + currentZ: number; - defaultOption: { + static defaultOption: CustomSeriesOption = { coordinateSystem: 'cartesian2d', // Can be set as 'none' zlevel: 0, z: 2, legendHoverLink: true, - useTransform: true, - // Custom series will not clip by default. // Some case will use custom series to draw label // For example https://echarts.apache.org/examples/en/editor.html?c=custom-gantt-flight - // Only works on polar and cartesian2d coordinate system. clip: false // Cartesian coordinate system @@ -105,43 +339,44 @@ SeriesModel.extend({ // label: {} // itemStyle: {} - }, + }; - /** - * @override - */ - getInitialData: function (option, ecModel) { + optionUpdated() { + this.currentZLevel = this.get('zlevel', true); + this.currentZ = this.get('z', true); + } + + getInitialData(option: CustomSeriesOption, ecModel: GlobalModel): List { return createListFromArray(this.getSource(), this); - }, + } - /** - * @override - */ - getDataParams: function (dataIndex, dataType, el) { - const params = SeriesModel.prototype.getDataParams.apply(this, arguments); - el && (params.info = el.info); + getDataParams(dataIndex: number, dataType: string, el: Element): CallbackDataParams & { + info: CustomExtraElementInfo + } { + const params = super.getDataParams(dataIndex, dataType, el) as ReturnType; + el && (params.info = inner(el).info); return params; } -}); +} -// ----- -// View -// ----- +ComponentModel.registerClass(CustomSeriesModel); -ChartView.extend({ - type: 'custom', - /** - * @private - * @type {module:echarts/data/List} - */ - _data: null, +class CustomSeriesView extends ChartView { - /** - * @override - */ - render: function (customSeries, ecModel, api, payload) { + static type = 'custom'; + readonly type = CustomSeriesView.type; + + private _data: List; + + + render( + customSeries: CustomSeriesModel, + ecModel: GlobalModel, + api: ExtensionAPI, + payload: Payload + ): void { const oldData = this._data; const data = customSeries.getData(); const group = this.group; @@ -182,17 +417,27 @@ ChartView.extend({ } this._data = data; - }, + } - incrementalPrepareRender: function (customSeries, ecModel, api) { + incrementalPrepareRender( + customSeries: CustomSeriesModel, + ecModel: GlobalModel, + api: ExtensionAPI + ): void { this.group.removeAll(); this._data = null; - }, + } - incrementalRender: function (params, customSeries, ecModel, api, payload) { + incrementalRender( + params: StageHandlerProgressParams, + customSeries: CustomSeriesModel, + ecModel: GlobalModel, + api: ExtensionAPI, + payload: Payload + ): void { const data = customSeries.getData(); const renderItem = makeRenderItem(customSeries, data, ecModel, api); - function setIncrementalAndHoverLayer(el) { + function setIncrementalAndHoverLayer(el: Displayable) { if (!el.isGroup) { el.incremental = true; el.useHoverLayer = true; @@ -202,17 +447,9 @@ ChartView.extend({ const el = createOrUpdate(null, idx, renderItem(idx, payload), customSeries, this.group, data); el.traverse(setIncrementalAndHoverLayer); } - }, - - /** - * @override - */ - dispose: zrUtil.noop, + } - /** - * @override - */ - filterForExposedEvent: function ( + filterForExposedEvent( eventType: string, query: EventQueryItem, targetEl: Element, packedEvent: ECEvent ): boolean { const elementName = query.element; @@ -230,17 +467,19 @@ ChartView.extend({ return false; } -}); +} + +ChartView.registerClass(CustomSeriesView); -function createEl(elOption) { +function createEl(elOption: CustomElementOption): Element { const graphicType = elOption.type; let el; // Those graphic elements are not shapes. They should not be // overwritten by users, so do them first. if (graphicType === 'path') { - const shape = elOption.shape; + const shape = (elOption as CustomSVGPathOption).shape; // Using pathRect brings convenience to users sacle svg path. const pathRect = (shape.width != null && shape.height != null) ? { @@ -248,20 +487,20 @@ function createEl(elOption) { y: shape.y || 0, width: shape.width, height: shape.height - } + } as RectLike : null; const pathData = getPathData(shape); // Path is also used for icon, so layout 'center' by default. el = graphicUtil.makePath(pathData, null, pathRect, shape.layout || 'center'); - el.__customPathData = pathData; + inner(el).customPathData = pathData; } else if (graphicType === 'image') { el = new graphicUtil.Image({}); - el.__customImagePath = elOption.style.image; + inner(el).customImagePath = (elOption as CustomImageOption).style.image; } else if (graphicType === 'text') { el = new graphicUtil.Text({}); - el.__customText = elOption.style.text; + inner(el).customText = (elOption.style as TextStyleProps).text; } else if (graphicType === 'group') { el = new graphicUtil.Group(); @@ -279,96 +518,305 @@ function createEl(elOption) { el = new Clz(); } - el.__customGraphicType = graphicType; + inner(el).customGraphicType = graphicType; el.name = elOption.name; + // Compat ec4: the default z2 lift is 1. If changing the number, + // some cases probably be broken: hierarchy layout along z, like circle packing, + // where emphasis only intending to modify color/border rather than lift z2. + (el as ECElement).z2EmphasisLift = 1; + return el; } -function updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot) { - const transitionProps = {}; - const elOptionStyle = elOption.style || {}; - - elOption.shape && (transitionProps.shape = zrUtil.clone(elOption.shape)); - elOption.position && (transitionProps.position = elOption.position.slice()); - elOption.scale && (transitionProps.scale = elOption.scale.slice()); - elOption.origin && (transitionProps.origin = elOption.origin.slice()); - elOption.rotation && (transitionProps.rotation = elOption.rotation); +/** + * [STRATEGY] Merge properties or erase all properties: + * + * Based on the fact that the existing zr element probably be reused, we discuss whether + * merge or erase all properties to the exsiting elements. + * + "Merge" means that if a certain props is not specified, do not assign to the existing element. + * + "Erase all" means that assign all of the available props whatever it specified by users. + * + * "Merge" might bring some unexpected state retaining for users and "erase all" seams to be + * more safe. But "erase all" force users to specify all of the props each time, which + * theoretically disables the chance of performance optimization (e.g., just generete shape + * and style at the first time rather than always do that). And "force user set all of the props" + * might bring trouble to specify which props need to perform "transition animation". + * So we still use "merge" rather than "erase all". If users need "erase all", they can + * simple always set all of the props each time. + * Some "object-like" config like `textConfig`, `textContent`, `style` which are not needed for + * every elment, so we replace them only when user specify them. And the that is a total replace. + * + * [STRATEGY] `hasOwnProperty` or `== null`: + * + * Ditinguishing "own property" probably bring little trouble to user when make el options. + * So we trade a {xx: null} or {xx: undefined} as "not specified" if possible rather than + * "set them to null/undefined". In most cases, props can not be cleared. Some typicall + * clearable props like `style`/`textConfig`/`textContent` we enable `false` to means + * "clear". In some othere special cases that the prop is able to set as null/undefined, + * but not suitable to use `false`, `hasOwnProperty` is checked. + */ +function updateElNormal( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + styleOpt: StyleOption, + attachedTxInfo: AttachedTxInfo, + seriesModel: CustomSeriesModel, + isInit: boolean, + isTextContent: boolean +): void { + const transitionProps = {} as ElementProps; + const elDisplayable = el.isGroup ? null : el as Displayable; + + (elOption as CustomZRPathOption).shape && ( + (transitionProps as PathProps).shape = zrUtil.clone((elOption as CustomZRPathOption).shape) + ); + setLagecyProp(elOption, transitionProps, 'position', 'x', 'y'); + setLagecyProp(elOption, transitionProps, 'scale', 'scaleX', 'scaleY'); + setLagecyProp(elOption, transitionProps, 'origin', 'originX', 'originY'); + setTransProp(elOption, transitionProps, 'x'); + setTransProp(elOption, transitionProps, 'y'); + setTransProp(elOption, transitionProps, 'scaleX'); + setTransProp(elOption, transitionProps, 'scaleY'); + setTransProp(elOption, transitionProps, 'originX'); + setTransProp(elOption, transitionProps, 'originY'); + setTransProp(elOption, transitionProps, 'rotation'); + + const txCfgOpt = attachedTxInfo && attachedTxInfo.normal.cfg; + if (txCfgOpt) { + // PENDING: whether use user object directly rather than clone? + // TODO:5.0 textConfig transition animation? + el.setTextConfig(txCfgOpt); + } - if (el.type === 'image' && elOption.style) { - const targetStyle = transitionProps.style = {}; - zrUtil.each(['x', 'y', 'width', 'height'], function (prop) { - prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit); - }); + if (el.type === 'image' && styleOpt) { + const targetStyle = (transitionProps as Displayable).style = {}; + const imgStyle = (el as graphicUtil.Image).style; + prepareStyleTransition('x', targetStyle, styleOpt, imgStyle, isInit); + prepareStyleTransition('y', targetStyle, styleOpt, imgStyle, isInit); + prepareStyleTransition('width', targetStyle, styleOpt, imgStyle, isInit); + prepareStyleTransition('height', targetStyle, styleOpt, imgStyle, isInit); } - if (el.type === 'text' && elOption.style) { - const targetStyle = transitionProps.style = {}; - zrUtil.each(['x', 'y'], function (prop) { - prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit); - }); - // Compatible with previous: both support - // textFill and fill, textStroke and stroke in 'text' element. - !elOptionStyle.hasOwnProperty('textFill') && elOptionStyle.fill && ( - elOptionStyle.textFill = elOptionStyle.fill + if (el.type === 'text' && styleOpt) { + const textOptionStyle = styleOpt as TextStyleProps; + const targetStyle = (transitionProps as Displayable).style = {}; + const textStyle = (el as graphicUtil.Text).style; + prepareStyleTransition('x', targetStyle, textOptionStyle, textStyle, isInit); + prepareStyleTransition('y', targetStyle, textOptionStyle, textStyle, isInit); + // Compatible with ec4: if `textFill` or `textStroke` exists use them. + zrUtil.hasOwn(textOptionStyle, 'textFill') && ( + textOptionStyle.fill = (textOptionStyle as any).textFill ); - !elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && ( - elOptionStyle.textStroke = elOptionStyle.stroke + zrUtil.hasOwn(textOptionStyle, 'textStroke') && ( + textOptionStyle.stroke = (textOptionStyle as any).textStroke ); } - if (el.type !== 'group') { - el.useStyle(elOptionStyle); + if (elDisplayable) { + // PENDING: here the input style object is used directly. + // Good for performance but bad for compatibility control. + styleOpt && elDisplayable.useStyle(styleOpt); // Init animation. if (isInit) { - el.style.opacity = 0; - let targetOpacity = elOptionStyle.opacity; - targetOpacity == null && (targetOpacity = 1); - graphicUtil.initProps(el, {style: {opacity: targetOpacity}}, animatableModel, dataIndex); + elDisplayable.style.opacity = 0; + const targetOpacity = (styleOpt && styleOpt.opacity != null) ? styleOpt.opacity : 1; + graphicUtil.initProps(elDisplayable, {style: {opacity: targetOpacity}}, seriesModel, dataIndex); } + + zrUtil.hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible); } if (isInit) { el.attr(transitionProps); } else { - graphicUtil.updateProps(el, transitionProps, animatableModel, dataIndex); + graphicUtil.updateProps(el, transitionProps, seriesModel, dataIndex); } // Merge by default. - // z2 must not be null/undefined, otherwise sort error may occur. - elOption.hasOwnProperty('z2') && el.attr('z2', elOption.z2 || 0); - elOption.hasOwnProperty('silent') && el.attr('silent', elOption.silent); - elOption.hasOwnProperty('invisible') && el.attr('invisible', elOption.invisible); - elOption.hasOwnProperty('ignore') && el.attr('ignore', elOption.ignore); - // `elOption.info` enables user to mount some info on - // elements and use them in event handlers. - // Update them only when user specified, otherwise, remain. - elOption.hasOwnProperty('info') && el.attr('info', elOption.info); - - // If `elOption.styleEmphasis` is `false`, remove hover style. The - // logic is ensured by `graphicUtil.setElementHoverStyle`. - const styleEmphasis = elOption.styleEmphasis; - // hoverStyle should always be set here, because if the hover style - // may already be changed, where the inner cache should be reset. - graphicUtil.enableElementHoverEmphasis(el, styleEmphasis); + zrUtil.hasOwn(elOption, 'silent') && (el.silent = elOption.silent); + zrUtil.hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore); + + if (!isTextContent) { + // `elOption.info` enables user to mount some info on + // elements and use them in event handlers. + // Update them only when user specified, otherwise, remain. + zrUtil.hasOwn(elOption, 'info') && (inner(el).info = elOption.info); + } + + el.markRedraw(); +} + +function updateElOnState( + state: DisplayStateNonNormal, + el: Element, + elStateOpt: CustomElementOptionOnState, + styleOpt: StyleOption, + attachedTxInfo: AttachedTxInfo, + isRoot: boolean, + isTextContent: boolean +): void { + const elDisplayable = el.isGroup ? null : el as Displayable; + const txCfgOpt = attachedTxInfo && attachedTxInfo[state].cfg; + + // PENDING:5.0 support customize scale change and transition animation? + + if (elDisplayable) { + // By default support auto lift color when hover whether `emphasis` specified. + const stateObj = elDisplayable.ensureState(state); + if (styleOpt === false) { + const existingEmphasisState = elDisplayable.getState(state); + if (existingEmphasisState) { + existingEmphasisState.style = null; + } + } + else { + // style is needed to enable defaut emphasis. + stateObj.style = styleOpt || {}; + } + // If `elOption.styleEmphasis` or `elOption.emphasis.style` is `false`, + // remove hover style. + // If `elOption.textConfig` or `elOption.emphasis.textConfig` is null/undefined, it does not + // make sense. So for simplicity, we do not ditinguish `hasOwnProperty` and null/undefined. + if (txCfgOpt) { + stateObj.textConfig = txCfgOpt; + } + + graphicUtil.enableElementHoverEmphasis(elDisplayable); + } + if (isRoot) { - graphicUtil.setAsHighDownDispatcher(el, styleEmphasis !== false); + graphicUtil.setAsHighDownDispatcher(el, styleOpt !== false); + } +} + +function updateZ( + el: Element, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + attachedTxInfo: AttachedTxInfo +): void { + // Group not support textContent and not support z yet. + if (el.isGroup) { + return; + } + + const elDisplayable = el as Displayable; + const currentZ = seriesModel.currentZ; + const currentZLevel = seriesModel.currentZLevel; + // Always erase. + elDisplayable.z = currentZ; + elDisplayable.zlevel = currentZLevel; + // z2 must not be null/undefined, otherwise sort error may occur. + const optZ2 = elOption.z2; + optZ2 != null && (elDisplayable.z2 = optZ2 || 0); + + const textContent = elDisplayable.getTextContent(); + if (textContent) { + textContent.z = currentZ; + textContent.zlevel = currentZLevel; + } + + updateZForEachState(elDisplayable, textContent, elOption, attachedTxInfo, NORMAL); + updateZForEachState(elDisplayable, textContent, elOption, attachedTxInfo, EMPHASIS); +} + +function updateZForEachState( + elDisplayable: Displayable, + textContent: Displayable, + elOption: CustomDisplayableOption, + attachedTxInfo: AttachedTxInfo, + state: DisplayState +): void { + const isNormal = state === NORMAL; + const elStateOpt = isNormal ? elOption : retrieveStateOption(elOption, state as DisplayStateNonNormal); + const optZ2 = elStateOpt ? elStateOpt.z2 : null; + let stateObj; + if (optZ2 != null) { + // Do not `ensureState` until required. + stateObj = isNormal ? elDisplayable : elDisplayable.ensureState(state); + stateObj.z2 = optZ2 || 0; + } + + const txConOpt = attachedTxInfo[state].conOpt; + if (textContent) { + const innerEl = inner(elDisplayable); + const txConZ2Set = innerEl.txConZ2Set || 0; + const txOptZ2 = txConOpt ? txConOpt.z2 : null; + const z2SetMask = 1 << Z2_SPECIFIED_BIT[state]; + + // Set textContent z2 as hostEl.z2 + 1 only if + // textContent z2 is not specified. + if (txOptZ2 != null) { + // Do not `ensureState` until required. + (isNormal ? textContent : textContent.ensureState(state)).z2 = txOptZ2; + innerEl.txConZ2Set = txConZ2Set | z2SetMask; + } + // If stateObj exists, that means stateObj.z2 has been updated, where the textContent z2 + // should be followed, no matter textContent or textContent.emphasis is specified in elOption. + else if (stateObj && (txConZ2Set & z2SetMask) === 0) { + (isNormal ? textContent : textContent.ensureState(state)).z2 = stateObj.z2 + 1; + } } } -function prepareStyleTransition(prop, targetStyle, elOptionStyle, oldElStyle, isInit) { +function setLagecyProp( + elOption: CustomElementOption, + transitionProps: Partial>, + legacyName: 'position' | 'scale' | 'origin', + xName: TransformPropsX, + yName: TransformPropsY +): void { + const legacyArr = (elOption as any)[legacyName]; + legacyArr && (transitionProps[xName] = legacyArr[0], transitionProps[yName] = legacyArr[1]); +} +function setTransProp( + elOption: CustomElementOption, + transitionProps: Partial>, + name: TransformProps +): void { + elOption[name] != null && (transitionProps[name] = elOption[name]); +} + +function prepareStyleTransition( + prop: 'x' | 'y', + targetStyle: CustomTextOption['style'], + elOptionStyle: CustomTextOption['style'], + oldElStyle: graphicUtil.Text['style'], + isInit: boolean +): void; +function prepareStyleTransition( + prop: 'x' | 'y' | 'width' | 'height', + targetStyle: CustomImageOption['style'], + elOptionStyle: CustomImageOption['style'], + oldElStyle: graphicUtil.Image['style'], + isInit: boolean +): void; +function prepareStyleTransition( + prop: string, + targetStyle: any, + elOptionStyle: any, + oldElStyle: any, + isInit: boolean +): void { if (elOptionStyle[prop] != null && !isInit) { targetStyle[prop] = elOptionStyle[prop]; elOptionStyle[prop] = oldElStyle[prop]; } } -function makeRenderItem(customSeries, data, ecModel, api) { +function makeRenderItem( + customSeries: CustomSeriesModel, + data: List, + ecModel: GlobalModel, + api: ExtensionAPI +) { const renderItem = customSeries.get('renderItem'); const coordSys = customSeries.coordinateSystem; - let prepareResult = {}; + let prepareResult = {} as ReturnType; if (coordSys) { if (__DEV__) { @@ -379,8 +827,9 @@ function makeRenderItem(customSeries, data, ecModel, api) { ); } + // `coordSys.prepareCustoms` is used for external coord sys like bmap. prepareResult = coordSys.prepareCustoms - ? coordSys.prepareCustoms() + ? coordSys.prepareCustoms(coordSys) : prepareCustoms[coordSys.type](coordSys); } @@ -396,9 +845,9 @@ function makeRenderItem(customSeries, data, ecModel, api) { barLayout: barLayout, currentSeriesIndices: currentSeriesIndices, font: font - }, prepareResult.api || {}); + }, prepareResult.api || {}) as CustomSeriesRenderItemAPI; - const userParams = { + const userParams: CustomSeriesRenderItemParams = { // The life cycle of context: current round of rendering. // The global life cycle is probably not necessary, because // user can store global status by themselves. @@ -411,17 +860,54 @@ function makeRenderItem(customSeries, data, ecModel, api) { encode: wrapEncodeDef(customSeries.getData()) }; + // If someday intending to refactor them to a class, should consider do not + // break change: currently these attribute member are encapsulated in a closure + // so that do not need to force user to call these method with a scope. + // Do not support call `api` asynchronously without dataIndexInside input. - let currDataIndexInside; - let currDirty = true; - let currItemModel; - let currLabelNormalModel; - let currLabelEmphasisModel; - let currVisualColor; - - return function (dataIndexInside, payload) { + let currDataIndexInside: number; + let currItemModel: Model; + let currItemStyleModels: Partial>> = {}; + let currLabelModels: Partial>> = {}; + + const seriesItemStyleModels = { + normal: customSeries.getModel(PATH_ITEM_STYLE.normal), + emphasis: customSeries.getModel(PATH_ITEM_STYLE.emphasis) + } as Record>; + const seriesLabelModels = { + normal: customSeries.getModel(PATH_LABEL.normal), + emphasis: customSeries.getModel(PATH_LABEL.emphasis) + } as Record>; + + function getItemModel(dataIndexInside: number): Model { + return dataIndexInside === currDataIndexInside + ? (currItemModel || (currItemModel = data.getItemModel(dataIndexInside))) + : data.getItemModel(dataIndexInside); + } + function getItemStyleModel(dataIndexInside: number, state: DisplayState) { + return !data.hasItemOption + ? seriesItemStyleModels[state] + : dataIndexInside === currDataIndexInside + ? (currItemStyleModels[state] || ( + currItemStyleModels[state] = getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state]) + )) + : getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state]); + } + function getLabelModel(dataIndexInside: number, state: DisplayState) { + return !data.hasItemOption + ? seriesLabelModels[state] + : dataIndexInside === currDataIndexInside + ? (currLabelModels[state] || ( + currLabelModels[state] = getItemModel(dataIndexInside).getModel(PATH_LABEL[state]) + )) + : getItemModel(dataIndexInside).getModel(PATH_LABEL[state]); + } + + return function (dataIndexInside: number, payload: Payload): CustomElementOption { currDataIndexInside = dataIndexInside; - currDirty = true; + currItemModel = null; + currItemStyleModels = {}; + currLabelModels = {}; return renderItem && renderItem( zrUtil.defaults({ @@ -434,158 +920,172 @@ function makeRenderItem(customSeries, data, ecModel, api) { ); }; - // Do not update cache until api called. - function updateCache(dataIndexInside) { - dataIndexInside == null && (dataIndexInside = currDataIndexInside); - if (currDirty) { - currItemModel = data.getItemModel(dataIndexInside); - currLabelNormalModel = currItemModel.getModel(LABEL_NORMAL); - currLabelEmphasisModel = currItemModel.getModel(LABEL_EMPHASIS); - currVisualColor = data.getItemVisual(dataIndexInside, 'color'); - - currDirty = false; - } - } - /** * @public - * @param {number|string} dim - * @param {number} [dataIndexInside=currDataIndexInside] - * @return {number|string} value + * @param dim by default 0. + * @param dataIndexInside by default `currDataIndexInside`. */ - function value(dim, dataIndexInside) { + function value(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue { dataIndexInside == null && (dataIndexInside = currDataIndexInside); return data.get(data.getDimension(dim || 0), dataIndexInside); } /** + * @deprecated The orgininal intention of `api.style` is enable to set itemStyle + * like other series. But it not necessary and not easy to give a strict definition + * of what it return. And since echarts5 it needs to be make compat work. So + * deprecates it since echarts5. + * * By default, `visual` is applied to style (to support visualMap). * `visual.color` is applied at `fill`. If user want apply visual.color on `stroke`, * it can be implemented as: * `api.style({stroke: api.visual('color'), fill: null})`; + * + * [Compat]: since ec5, RectText has been separated from its hosts el. + * so `api.style()` will only return the style from `itemStyle` but not handle `label` + * any more. But `series.label` config is never published in doc. + * We still compat it in `api.style()`. But not encourage to use it and will still not + * to pulish it to doc. * @public - * @param {Object} [extra] - * @param {number} [dataIndexInside=currDataIndexInside] + * @param dataIndexInside by default `currDataIndexInside`. */ - function style(extra, dataIndexInside) { + function style(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps { + if (__DEV__) { + warnDeprecated('api.style', 'Please write literal style directly instead.'); + } + dataIndexInside == null && (dataIndexInside = currDataIndexInside); - updateCache(dataIndexInside); - const itemStyle = currItemModel.getModel(ITEM_STYLE_NORMAL_PATH).getItemStyle(); + const style = data.getItemVisual(dataIndexInside, 'style'); + const visualColor = style && style.fill; + const opacity = style && style.opacity; - currVisualColor != null && (itemStyle.fill = currVisualColor); - const opacity = data.getItemVisual(dataIndexInside, 'opacity'); + let itemStyle = getItemStyleModel(dataIndexInside, NORMAL).getItemStyle(); + visualColor != null && (itemStyle.fill = visualColor); opacity != null && (itemStyle.opacity = opacity); - const labelModel = extra - ? applyExtraBefore(extra, currLabelNormalModel) - : currLabelNormalModel; - - const textStyle = graphicUtil.createTextStyle(labelModel, null, { - autoColor: currVisualColor, - isRectText: true - }); - - // TODO - zrUtil.extend(itemStyle, textStyle); - - itemStyle.text = labelModel.getShallow('show') + const opt = {autoColor: zrUtil.isString(visualColor) ? visualColor : '#000'}; + const labelModel = getLabelModel(dataIndexInside, NORMAL); + // Now that the feture of "auto adjust text fill/stroke" has been migrated to zrender + // since ec5, we should set `isAttached` as `false` here and make compat in + // `convertToEC4StyleForCustomSerise`. + const textStyle = graphicUtil.createTextStyle(labelModel, null, opt, false, true); + textStyle.text = labelModel.getShallow('show') ? zrUtil.retrieve2( - customSeries.getFormattedLabel(dataIndexInside, 'normal'), + customSeries.getFormattedLabel(dataIndexInside, NORMAL), getDefaultLabel(data, dataIndexInside) ) : null; + const textConfig = graphicUtil.createTextConfig(textStyle, labelModel, opt, false); + + preFetchFromExtra(extra, itemStyle); + itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig); extra && applyExtraAfter(itemStyle, extra); + (itemStyle as LegacyStyleProps).legacy = true; return itemStyle; } /** + * @deprecated The reason see `api.style()` * @public - * @param {Object} [extra] - * @param {number} [dataIndexInside=currDataIndexInside] + * @param dataIndexInside by default `currDataIndexInside`. */ - function styleEmphasis(extra, dataIndexInside) { - dataIndexInside == null && (dataIndexInside = currDataIndexInside); - updateCache(dataIndexInside); - - const itemStyle = currItemModel.getModel(ITEM_STYLE_EMPHASIS_PATH).getItemStyle(); - - const labelModel = extra - ? applyExtraBefore(extra, currLabelEmphasisModel) - : currLabelEmphasisModel; - - const textStyle = graphicUtil.createTextStyle(labelModel, null, { - isRectText: true - }, true); - zrUtil.extend(itemStyle, textStyle); + function styleEmphasis(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps { + if (__DEV__) { + warnDeprecated('api.styleEmphasis', 'Please write literal style directly instead.'); + } + dataIndexInside == null && (dataIndexInside = currDataIndexInside); - itemStyle.text = labelModel.getShallow('show') + let itemStyle = getItemStyleModel(dataIndexInside, EMPHASIS).getItemStyle(); + const labelModel = getLabelModel(dataIndexInside, EMPHASIS); + const textStyle = graphicUtil.createTextStyle(labelModel, null, null, true, true); + textStyle.text = labelModel.getShallow('show') ? zrUtil.retrieve3( - customSeries.getFormattedLabel(dataIndexInside, 'emphasis'), - customSeries.getFormattedLabel(dataIndexInside, 'normal'), + customSeries.getFormattedLabel(dataIndexInside, EMPHASIS), + customSeries.getFormattedLabel(dataIndexInside, NORMAL), getDefaultLabel(data, dataIndexInside) ) : null; + const textConfig = graphicUtil.createTextConfig(textStyle, labelModel, null, true); + + preFetchFromExtra(extra, itemStyle); + itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig); extra && applyExtraAfter(itemStyle, extra); + (itemStyle as LegacyStyleProps).legacy = true; return itemStyle; } + function preFetchFromExtra(extra: ZRStyleProps, itemStyle: ItemStyleProps): void { + // A trick to retrieve those props firstly, which are used to + // apply auto inside fill/stroke in `convertToEC4StyleForCustomSerise`. + // (It's not reasonable but only for a degree of compat) + if (extra) { + (extra as any).textFill && ((itemStyle as any).textFill = (extra as any).textFill); + (extra as any).textPosition && ((itemStyle as any).textPosition = (extra as any).textPosition); + } + } + /** * @public - * @param {string} visualType - * @param {number} [dataIndexInside=currDataIndexInside] + * @param dataIndexInside by default `currDataIndexInside`. */ - function visual(visualType, dataIndexInside) { + function visual( + visualType: keyof DefaultDataVisual, + dataIndexInside?: number + ): ReturnType { dataIndexInside == null && (dataIndexInside = currDataIndexInside); - return data.getItemVisual(dataIndexInside, visualType); + + if (zrUtil.hasOwn(STYLE_VISUAL_TYPE, visualType)) { + const style = data.getItemVisual(dataIndexInside, 'style'); + return style + ? style[STYLE_VISUAL_TYPE[visualType as keyof typeof STYLE_VISUAL_TYPE]] as any + : null; + } + // Only support these visuals. Other visual might be inner tricky + // for performance (like `style`), do not expose to users. + if (zrUtil.hasOwn(VISUAL_PROPS, visualType)) { + return data.getItemVisual(dataIndexInside, visualType); + } } /** * @public - * @param {number} opt.count Positive interger. - * @param {number} [opt.barWidth] - * @param {number} [opt.barMaxWidth] - * @param {number} [opt.barMinWidth] - * @param {number} [opt.barGap] - * @param {number} [opt.barCategoryGap] - * @return {Object} {width, offset, offsetCenter} is not support, return undefined. + * @return If not support, return undefined. */ - function barLayout(opt) { - if (coordSys.getBaseAxis) { - const baseAxis = coordSys.getBaseAxis(); - return getLayoutOnAxis(zrUtil.defaults({axis: baseAxis}, opt), api); + function barLayout( + opt: Omit[0], 'axis'> + ): ReturnType { + if (coordSys.type === 'cartesian2d') { + const baseAxis = coordSys.getBaseAxis() as Axis2D; + return getLayoutOnAxis(zrUtil.defaults({axis: baseAxis}, opt)); } } /** * @public - * @return {Array.} */ - function currentSeriesIndices() { + function currentSeriesIndices(): ReturnType { return ecModel.getCurrentSeriesIndices(); } /** * @public - * @param {Object} opt - * @param {string} [opt.fontStyle] - * @param {number} [opt.fontWeight] - * @param {number} [opt.fontSize] - * @param {string} [opt.fontFamily] - * @return {string} font string + * @return font string */ - function font(opt) { + function font( + opt: Parameters[0] + ): ReturnType { return graphicUtil.getFont(opt, ecModel); } } -function wrapEncodeDef(data) { - const encodeDef = {}; +function wrapEncodeDef(data: List): Dictionary { + const encodeDef = {} as Dictionary; zrUtil.each(data.dimensions, function (dimName, dataDimIndex) { const dimInfo = data.getDimensionInfo(dimName); if (!dimInfo.isExtraCoord) { @@ -597,14 +1097,29 @@ function wrapEncodeDef(data) { return encodeDef; } -function createOrUpdate(el, dataIndex, elOption, animatableModel, group, data) { - el = doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, true); +function createOrUpdate( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + group: ViewRootGroup, + data: List +): Element { + el = doCreateOrUpdate(el, dataIndex, elOption, seriesModel, group, data, true); el && data.setItemGraphicEl(dataIndex, el); return el; } -function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, isRoot) { +function doCreateOrUpdate( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + group: ViewRootGroup, + data: List, + isRoot: boolean +): Element { // [Rule] // By default, follow merge mode. @@ -616,45 +1131,75 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, // regard "return;" as "show nothing element whatever", so make a exception to meet the // most cases.) - const simplyRemove = !elOption; // `null`/`undefined`/`false` - elOption = elOption || {}; + // If `elOption` is `null`/`undefined`/`false` (when `renderItem` returns nothing). + if (!elOption) { + el && group.remove(el); + return; + } + + elOption = elOption || {} as CustomElementOption; const elOptionType = elOption.type; - const elOptionShape = elOption.shape; + const elOptionShape = (elOption as CustomZRPathOption).shape; const elOptionStyle = elOption.style; - if (el && ( - simplyRemove - // || elOption.$merge === false - // If `elOptionType` is `null`, follow the merge principle. - || (elOptionType != null - && elOptionType !== el.__customGraphicType - ) - || (elOptionType === 'path' - && hasOwnPathData(elOptionShape) && getPathData(elOptionShape) !== el.__customPathData - ) - || (elOptionType === 'image' - && hasOwn(elOptionStyle, 'image') && elOptionStyle.image !== el.__customImagePath - ) - // FIXME test and remove this restriction? - || (elOptionType === 'text' - && hasOwn(elOptionShape, 'text') && elOptionStyle.text !== el.__customText - ) - )) { - group.remove(el); - el = null; + if (el) { + const elInner = inner(el); + if ( + // || elOption.$merge === false + // If `elOptionType` is `null`, follow the merge principle. + (elOptionType != null + && elOptionType !== elInner.customGraphicType + ) + || (elOptionType === 'path' + && hasOwnPathData(elOptionShape) + && getPathData(elOptionShape) !== elInner.customPathData + ) + || (elOptionType === 'image' + && zrUtil.hasOwn(elOptionStyle, 'image') + && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath + ) + // FIXME test and remove this restriction? + || (elOptionType === 'text' + && zrUtil.hasOwn(elOptionStyle, 'text') + && (elOptionStyle as TextStyleProps).text !== elInner.customText + ) + ) { + group.remove(el); + el = null; + } } - // `elOption.type` is undefined when `renderItem` returns nothing. - if (simplyRemove) { - return; + const isInit = !el; + + if (!el) { + el = createEl(elOption); + } + else { + // If in some case the performance issue arised, consider + // do not clearState but update cached normal state directly. + el.clearStates(); } - const isInit = !el; - !el && (el = createEl(elOption)); - updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot); + attachedTxInfoTmp.normal.cfg = attachedTxInfoTmp.normal.conOpt = + attachedTxInfoTmp.emphasis.cfg = attachedTxInfoTmp.emphasis.conOpt = null; + attachedTxInfoTmp.isLegacy = false; + + doCreateOrUpdateAttachedTx( + el, dataIndex, elOption, seriesModel, isInit, attachedTxInfoTmp + ); + + const stateOptEmphasis = retrieveStateOption(elOption, EMPHASIS); + const styleOptEmphasis = retrieveStyleOptionOnState(elOption, stateOptEmphasis, EMPHASIS); + + updateElNormal(el, dataIndex, elOption, elOption.style, attachedTxInfoTmp, seriesModel, isInit, false); + updateElOnState(EMPHASIS, el, stateOptEmphasis, styleOptEmphasis, attachedTxInfoTmp, isRoot, false); + + updateZ(el, elOption, seriesModel, attachedTxInfoTmp); if (elOptionType === 'group') { - mergeChildren(el, dataIndex, elOption, animatableModel, data); + mergeChildren( + el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, data + ); } // Always add whatever already added to ensure sequence. @@ -663,6 +1208,137 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, return el; } +function doCreateOrUpdateAttachedTx( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + isInit: boolean, + attachedTxInfo: AttachedTxInfo +): void { + // group do not support textContent temporarily untill necessary. + if (el.isGroup) { + return; + } + + // Normal must be called before emphasis, for `isLegacy` detection. + processTxInfo(elOption, null, attachedTxInfo); + processTxInfo(elOption, EMPHASIS, attachedTxInfo); + + // If `elOption.textConfig` or `elOption.textContent` is null/undefined, it does not make sence. + // So for simplicity, if "elOption hasOwnProperty of them but be null/undefined", we do not + // trade them as set to null to el. + // Especially: + // `elOption.textContent: false` means remove textContent. + // `elOption.textContent.emphasis.style: false` means remove the style from emphasis state. + let txConOptNormal = attachedTxInfo.normal.conOpt as CustomElementOption | false; + const txConOptEmphasis = attachedTxInfo.emphasis.conOpt as CustomElementOptionOnState; + + if (txConOptEmphasis != null) { + // If textContent has emphasis state, el should auto has emphasis + // state, otherwise it can not be triggered. + el.ensureState(EMPHASIS); + } + + if (txConOptNormal != null || txConOptEmphasis != null) { + let textContent = el.getTextContent(); + if (txConOptNormal === false) { + textContent && el.removeTextContent(); + } + else { + txConOptNormal = attachedTxInfo.normal.conOpt = txConOptNormal || {type: 'text'}; + if (!textContent) { + textContent = createEl(txConOptNormal) as graphicUtil.Text; + el.setTextContent(textContent); + } + else { + // If in some case the performance issue arised, consider + // do not clearState but update cached normal state directly. + textContent.clearStates(); + } + const txConStlOptNormal = txConOptNormal && txConOptNormal.style; + + updateElNormal( + textContent, dataIndex, txConOptNormal, txConStlOptNormal, null, seriesModel, isInit, true + ); + const txConStlOptEmphasis = retrieveStyleOptionOnState(txConOptNormal, txConOptEmphasis, EMPHASIS); + updateElOnState(EMPHASIS, textContent, txConOptEmphasis, txConStlOptEmphasis, null, false, true); + + textContent.markRedraw(); + } + } +} + +function processTxInfo( + elOption: CustomElementOption, + state: DisplayStateNonNormal, + attachedTxInfo: AttachedTxInfo +): void { + const stateOpt = !state ? elOption : retrieveStateOption(elOption, state); + const styleOpt = !state ? elOption.style : retrieveStyleOptionOnState(elOption, stateOpt, EMPHASIS); + + const elType = elOption.type; + let txCfg = stateOpt ? stateOpt.textConfig : null; + const txConOptNormal = elOption.textContent; + let txConOpt: CustomElementOption | CustomElementOptionOnState = + !txConOptNormal ? null : !state ? txConOptNormal : retrieveStateOption(txConOptNormal, state); + + if (styleOpt && ( + // Because emphasis style has little info to detect legacy, + // if normal is legacy, emphasis is trade as legacy. + attachedTxInfo.isLegacy + || isEC4CompatibleStyle(styleOpt, elType, !!txCfg, !!txConOpt) + )) { + attachedTxInfo.isLegacy = true; + const convertResult = convertFromEC4CompatibleStyle(styleOpt, elType, !state); + // Explicitly specified `textConfig` and `textContent` has higher priority than + // the ones generated by legacy style. Otherwise if users use them and `api.style` + // at the same time, they not both work and hardly to known why. + if (!txCfg && convertResult.textConfig) { + txCfg = convertResult.textConfig; + } + if (!txConOpt && convertResult.textContent) { + txConOpt = convertResult.textContent; + } + } + + if (!state && txConOpt) { + const txConOptNormal = txConOpt as CustomElementOption; + // `textContent: {type: 'text'}`, the "type" is easy to be missing. So we tolerate it. + !txConOptNormal.type && (txConOptNormal.type = 'text'); + if (__DEV__) { + // Do not tolerate incorret type for forward compat. + txConOptNormal.type !== 'text' && zrUtil.assert( + txConOptNormal.type === 'text', + 'textContent.type must be "text"' + ); + } + } + + const info = !state ? attachedTxInfo.normal : attachedTxInfo[state]; + info.cfg = txCfg; + info.conOpt = txConOpt; +} + +function retrieveStateOption( + elOption: CustomElementOption, state: DisplayStateNonNormal +): CustomElementOptionOnState { + return !state ? elOption : elOption ? elOption[state] : null; +} + +function retrieveStyleOptionOnState( + stateOptionNormal: CustomElementOption, + stateOption: CustomElementOptionOnState, + state: DisplayStateNonNormal +): StyleOption { + let style = stateOption && stateOption.style; + if (style == null && state === EMPHASIS && stateOptionNormal) { + style = stateOptionNormal.styleEmphasis; + } + return style; +} + + // Usage: // (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates that // the existing children will not be removed, and enables the feature that @@ -679,7 +1355,14 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, // child (otherwise the total indicies of the children array have to be modified). // User can remove a single child by set its `ignore` as `true` or replace // it by another element, where its `$merge` can be set as `true` if necessary. -function mergeChildren(el, dataIndex, elOption, animatableModel, data) { +function mergeChildren( + el: graphicUtil.Group, + dataIndex: number, + elOption: CustomGroupOption, + seriesModel: CustomSeriesModel, + data: List +): void { + const newChildren = elOption.children; const newLen = newChildren ? newChildren.length : 0; const mergeChildren = elOption.$mergeChildren; @@ -697,7 +1380,7 @@ function mergeChildren(el, dataIndex, elOption, animatableModel, data) { oldChildren: el.children() || [], newChildren: newChildren || [], dataIndex: dataIndex, - animatableModel: animatableModel, + seriesModel: seriesModel, group: el, data: data }); @@ -714,9 +1397,10 @@ function mergeChildren(el, dataIndex, elOption, animatableModel, data) { el.childAt(index), dataIndex, newChildren[index], - animatableModel, + seriesModel, el, - data + data, + false ); } if (__DEV__) { @@ -727,7 +1411,15 @@ function mergeChildren(el, dataIndex, elOption, animatableModel, data) { } } -function diffGroupChildren(context) { +type DiffGroupContext = { + oldChildren: Element[], + newChildren: CustomElementOption[], + dataIndex: number, + seriesModel: CustomSeriesModel, + group: graphicUtil.Group, + data: List +}; +function diffGroupChildren(context: DiffGroupContext) { (new DataDiffer( context.oldChildren, context.newChildren, @@ -741,12 +1433,16 @@ function diffGroupChildren(context) { .execute(); } -function getKey(item, idx) { +function getKey(item: Element, idx: number): string { const name = item && item.name; return name != null ? name : GROUP_DIFF_PREFIX + idx; } -function processAddUpdate(newIndex, oldIndex) { +function processAddUpdate( + this: DataDiffer, + newIndex: number, + oldIndex?: number +): void { const context = this.context; const childOption = newIndex != null ? context.newChildren[newIndex] : null; const child = oldIndex != null ? context.oldChildren[oldIndex] : null; @@ -755,50 +1451,36 @@ function processAddUpdate(newIndex, oldIndex) { child, context.dataIndex, childOption, - context.animatableModel, + context.seriesModel, context.group, - context.data + context.data, + false ); } -// `graphic#applyDefaultTextStyle` will cache -// textFill, textStroke, textStrokeWidth. -// We have to do this trick. -function applyExtraBefore(extra, model) { - const dummyModel = new Model({}, model); - zrUtil.each(CACHED_LABEL_STYLE_PROPERTIES, function (stylePropName, modelPropName) { - if (extra.hasOwnProperty(stylePropName)) { - dummyModel.option[modelPropName] = extra[stylePropName]; - } - }); - return dummyModel; -} - -function applyExtraAfter(itemStyle, extra) { +function applyExtraAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void { for (const key in extra) { - if (extra.hasOwnProperty(key) - || !CACHED_LABEL_STYLE_PROPERTIES.hasOwnProperty(key) - ) { - itemStyle[key] = extra[key]; + if (zrUtil.hasOwn(extra, key)) { + (itemStyle as any)[key] = (extra as any)[key]; } } } -function processRemove(oldIndex) { +function processRemove(this: DataDiffer, oldIndex: number): void { const context = this.context; const child = context.oldChildren[oldIndex]; child && context.group.remove(child); } -function getPathData(shape) { +/** + * @return SVG Path data. + */ +function getPathData(shape: CustomSVGPathOption['shape']): string { // "d" follows the SVG convention. return shape && (shape.pathData || shape.d); } -function hasOwnPathData(shape) { - return shape && (shape.hasOwnProperty('pathData') || shape.hasOwnProperty('d')); +function hasOwnPathData(shape: CustomSVGPathOption['shape']): boolean { + return shape && (zrUtil.hasOwn(shape, 'pathData') || zrUtil.hasOwn(shape, 'd')); } -function hasOwn(host, prop) { - return host && host.hasOwnProperty(prop); -} diff --git a/src/coord/CoordinateSystem.ts b/src/coord/CoordinateSystem.ts index 2af741a815..84fd2f569d 100644 --- a/src/coord/CoordinateSystem.ts +++ b/src/coord/CoordinateSystem.ts @@ -26,6 +26,7 @@ import { BoundingRect } from '../util/graphic'; import { MatrixArray } from 'zrender/src/core/matrix'; import ComponentModel from '../model/Component'; import { RectLike } from 'zrender/src/core/BoundingRect'; +import { PrepareCustomInfo } from '../chart/custom'; export interface CoordinateSystemCreator { @@ -151,6 +152,8 @@ export interface CoordinateSystem { // Currently only Cartesian2D implements it. // But if other coordinate systems implement it, should follow this signature. getAxesByScale?: (scaleType: string) => Axis[]; + + prepareCustoms?: PrepareCustomInfo; } /** diff --git a/src/echarts.ts b/src/echarts.ts index d60e366799..08fb940da6 100644 --- a/src/echarts.ts +++ b/src/echarts.ts @@ -1729,6 +1729,9 @@ class ECharts extends Eventful { }; updateZ = function (model: ComponentModel, view: ComponentView | ChartView): void { + if (model.preventAutoZ) { + return; + } const z = model.get('z'); const zlevel = model.get('zlevel'); // Set z and zlevel @@ -1743,6 +1746,7 @@ class ECharts extends Eventful { textContent.z = el.z; textContent.zlevel = el.zlevel; // lift z2 of text content + // TODO if el.emphasis.z2 is spcefied, what about textContent. textContent.z2 = el.z2 + 1; } } diff --git a/src/model/Component.ts b/src/model/Component.ts index 0d2b0c0aaf..d1a35e98ab 100644 --- a/src/model/Component.ts +++ b/src/model/Component.ts @@ -123,6 +123,11 @@ class ComponentModel extends Mode */ static layoutMode: ComponentLayoutMode | ComponentLayoutMode['type']; + /** + * Prevent from auto set z, zlevel, z2 by the framework. + */ + preventAutoZ: boolean; + // Injectable properties: __viewId: string; diff --git a/src/model/mixin/itemStyle.ts b/src/model/mixin/itemStyle.ts index c0c88e0370..129c67fbe6 100644 --- a/src/model/mixin/itemStyle.ts +++ b/src/model/mixin/itemStyle.ts @@ -44,7 +44,7 @@ type ItemStyleKeys = 'fill' | 'shadowOffsetY' | 'shadowColor'; -type ItemStyleProps = Pick; +export type ItemStyleProps = Pick; class ItemStyleMixin { diff --git a/src/util/graphic.ts b/src/util/graphic.ts index 2875480170..cadfa38e7f 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -58,7 +58,8 @@ import { ColorString, DataModel, ECEventData, - ZRStyleProps + ZRStyleProps, + TextCommonOption } from './types'; import GlobalModel from '../model/Global'; import { makeInner } from './model'; @@ -81,13 +82,6 @@ const EMPTY_OBJ = {}; export const Z2_EMPHASIS_LIFT = 10; -// key: label model property nane, value: style property name. -export const CACHED_LABEL_STYLE_PROPERTIES = { - color: 'textFill', - textBorderColor: 'textStroke', - textBorderWidth: 'textStrokeWidth' -}; - const EMPHASIS = 'emphasis'; const NORMAL = 'normal'; @@ -126,8 +120,6 @@ type TextCommonParams = { forceRich?: boolean - getTextPosition?: (textStyleModel: Model, isEmphasis?: boolean) => string | string[] | number[] - defaultOutsidePosition?: LabelOption['position'] textStyle?: ZRStyleProps @@ -390,12 +382,14 @@ function singleEnterEmphasis(el: Element) { if (!hasFillOrStroke(emphasisStyle.stroke)) { disp.style.stroke = liftColor(currentStroke); } - disp.z2 += Z2_EMPHASIS_LIFT; + const z2EmphasisLift = (disp as ECElement).z2EmphasisLift; + disp.z2 += z2EmphasisLift != null ? z2EmphasisLift : Z2_EMPHASIS_LIFT; } const textContent = el.getTextContent(); if (textContent) { - textContent.z2 += Z2_EMPHASIS_LIFT; + const z2EmphasisLift = (textContent as ECElement).z2EmphasisLift; + textContent.z2 += z2EmphasisLift != null ? z2EmphasisLift : Z2_EMPHASIS_LIFT; } // TODO hover layer } @@ -770,6 +764,7 @@ export function createTextConfig( opt: TextCommonParams, isEmphasis: boolean ) { + opt = opt || {}; const textConfig: ElementTextConfig = {}; let labelPosition; let labelRotate = textStyleModel.getShallow('rotate'); @@ -778,16 +773,11 @@ export function createTextConfig( ); const labelOffset = textStyleModel.getShallow('offset'); - if (opt.getTextPosition) { - labelPosition = opt.getTextPosition(textStyleModel, isEmphasis); - } - else { - labelPosition = textStyleModel.getShallow('position') - || (isEmphasis ? null : 'inside'); - // 'outside' is not a valid zr textPostion value, but used - // in bar series, and magric type should be considered. - labelPosition === 'outside' && (labelPosition = opt.defaultOutsidePosition || 'top'); - } + labelPosition = textStyleModel.getShallow('position') + || (isEmphasis ? null : 'inside'); + // 'outside' is not a valid zr textPostion value, but used + // in bar series, and magric type should be considered. + labelPosition === 'outside' && (labelPosition = opt.defaultOutsidePosition || 'top'); if (labelPosition != null) { textConfig.position = labelPosition; @@ -1031,7 +1021,10 @@ function setTokenTextStyle( } } -export function getFont(opt: LabelOption, ecModel: GlobalModel) { +export function getFont( + opt: Pick, + ecModel: GlobalModel +) { const gTextStyleModel = ecModel && ecModel.getModel('textStyle'); return trim([ // FIXME in node-canvas fontWeight is before fontStyle diff --git a/src/util/styleCompat.ts b/src/util/styleCompat.ts new file mode 100644 index 0000000000..b7e1150ad9 --- /dev/null +++ b/src/util/styleCompat.ts @@ -0,0 +1,256 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { Dictionary, ZRStyleProps } from './types'; +import { ElementTextConfig } from 'zrender/src/Element'; +import { TextStyleProps, TextStylePropsPart, TextProps } from 'zrender/src/graphic/Text'; +import { each, hasOwn } from 'zrender/src/core/util'; +import { __DEV__ } from '../config'; +import { ItemStyleProps } from '../model/mixin/itemStyle'; + +export interface LegacyStyleProps { + legacy?: boolean +} + +const deprecatedLogs = {} as Dictionary; + +/** + * Whether need to call `convertEC4CompatibleStyle`. + */ +export function isEC4CompatibleStyle( + style: ZRStyleProps & LegacyStyleProps, + elType: string, + hasOwnTextContentOption: boolean, + hasOwnTextConfig: boolean +): boolean { + // Since echarts5, `RectText` is separated from its host element and style.text + // does not exist any more. The compat work brings some extra burden on performance. + // So we provide: + // `legacy: true` force make compat. + // `legacy: false`, force do not compat. + // `legacy` not set: auto detect wheter legacy. + // But in this case we do not compat (difficult to detect and rare case): + // Becuse custom series and graphic component support "merge", users may firstly + // only set `textStrokeWidth` style or secondly only set `text`. + return style && ( + style.legacy + || ( + style.legacy !== false + && !hasOwnTextContentOption + && !hasOwnTextConfig + && elType !== 'tspan' + // Difficult to detect whether legacy for a "text" el. + && (elType === 'text' || hasOwn(style, 'text')) + ) + ); +} + +/** + * `EC4CompatibleStyle` is style that might be in echarts4 format or echarts5 format. + * @param hostStyle The properties might be modified. + * @return If be text el, `textContentStyle` and `textConfig` will not be retured. + * Otherwise a `textContentStyle` and `textConfig` will be created, whose props area + * retried from the `hostStyle`. + */ +export function convertFromEC4CompatibleStyle(hostStyle: ZRStyleProps, elType: string, isNormal: boolean): { + textContent: TextProps & {type: string}, + textConfig: ElementTextConfig +} { + const srcStyle = hostStyle as Dictionary; + let textConfig: ElementTextConfig; + let textContent: TextProps & {type: string}; + + let textContentStyle: TextStyleProps; + if (elType === 'text') { + textContentStyle = srcStyle; + } + else { + textContentStyle = {}; + hasOwn(srcStyle, 'text') && (textContentStyle.text = srcStyle.text); + hasOwn(srcStyle, 'rich') && (textContentStyle.rich = srcStyle.rich); + hasOwn(srcStyle, 'textFill') && (textContentStyle.fill = srcStyle.textFill); + hasOwn(srcStyle, 'textStroke') && (textContentStyle.stroke = srcStyle.textStroke); + + textContent = { + type: 'text', + style: textContentStyle, + // ec4 do not support rectText trigger. + // And when text postion is different in normal and emphasis + // => hover text trigger emphasis; + // => text position changed, leave mouse pointer immediately; + // That might cause state incorrect. + silent: true + }; + textConfig = {}; + const hasOwnPos = hasOwn(srcStyle, 'textPosition'); + if (isNormal) { + textConfig.position = hasOwnPos ? srcStyle.textPosition : 'inside'; + } + else { + hasOwnPos && (textConfig.position = srcStyle.textPosition); + } + hasOwn(srcStyle, 'textPosition') && (textConfig.position = srcStyle.textPosition); + hasOwn(srcStyle, 'textOffset') && (textConfig.offset = srcStyle.textOffset); + hasOwn(srcStyle, 'textRotation') && (textConfig.rotation = srcStyle.textRotation); + hasOwn(srcStyle, 'textDistance') && (textConfig.distance = srcStyle.textDistance); + } + + convertEC4CompatibleRichItem(textContentStyle, hostStyle); + + each(textContentStyle.rich, function (richItem) { + convertEC4CompatibleRichItem(richItem as TextStyleProps, richItem); + }); + + return { + textConfig: textConfig, + textContent: textContent + }; +} + +/** + * The result will be set to `out`. + */ +function convertEC4CompatibleRichItem(out: TextStylePropsPart, richItem: Dictionary): void { + if (!richItem) { + return; + } + // (1) For simplicity, make textXXX properties (deprecated since ec5) has + // higher priority. For example, consider in ec4 `borderColor: 5, textBorderColor: 10` + // on a rect means `borderColor: 4` on the rect and `borderColor: 10` on an attached + // richText in ec5. + // (2) `out === richItem` if and only if `out` is text el or rich item. + // So we can overwite existing props in `out` since textXXX has higher priority. + richItem.font = richItem.textFont || richItem.font; + hasOwn(richItem, 'textStrokeWidth') && (out.lineWidth = richItem.textStrokeWidth); + hasOwn(richItem, 'textAlign') && (out.align = richItem.textAlign); + hasOwn(richItem, 'textVerticalAlign') && (out.verticalAlign = richItem.textVerticalAlign); + hasOwn(richItem, 'textLineHeight') && (out.lineHeight = richItem.textLineHeight); + hasOwn(richItem, 'textWidth') && (out.width = richItem.textWidth); + hasOwn(richItem, 'textHeight') && (out.height = richItem.textHeight); + hasOwn(richItem, 'textBackgroundColor') && (out.backgroundColor = richItem.textBackgroundColor); + hasOwn(richItem, 'textPadding') && (out.padding = richItem.textPadding); + hasOwn(richItem, 'textBorderColor') && (out.borderColor = richItem.textBorderColor); + hasOwn(richItem, 'textBorderWidth') && (out.borderWidth = richItem.textBorderWidth); + hasOwn(richItem, 'textBorderRadius') && (out.borderRadius = richItem.textBorderRadius); + hasOwn(richItem, 'textBoxShadowColor') && (out.shadowColor = richItem.textBoxShadowColor); + hasOwn(richItem, 'textBoxShadowBlur') && (out.shadowBlur = richItem.textBoxShadowBlur); + hasOwn(richItem, 'textBoxShadowOffsetX') && (out.shadowOffsetX = richItem.textBoxShadowOffsetX); + hasOwn(richItem, 'textBoxShadowOffsetY') && (out.shadowOffsetY = richItem.textBoxShadowOffsetY); +} + +/** + * Convert to pure echarts4 format style. + * `itemStyle` will be modified, added with ec4 style properties from + * `textStyle` and `textConfig`. + * + * [Caveat]: For simplicity, `insideRollback` in ec4 does not compat, where + * `styleEmphasis: {textFill: 'red'}` will remove the normal auto added stroke. + */ +export function convertToEC4StyleForCustomSerise( + itemStl: ItemStyleProps, + txStl: TextStyleProps, + txCfg: ElementTextConfig +): ZRStyleProps { + + const out = itemStl as Dictionary; + + // See `custom.ts`, a trick to set extra `textPosition` firstly. + out.textPosition = out.textPosition || txCfg.position || 'inside'; + txCfg.offset != null && (out.textOffset = txCfg.offset); + txCfg.rotation != null && (out.textRotation = txCfg.rotation); + txCfg.distance != null && (out.textDistance = txCfg.distance); + + const isInside = (out.textPosition as string).indexOf('inside') >= 0; + const hostFill = itemStl.fill || '#000'; + + convertToEC4RichItem(out, txStl); + + const textFillNotSet = out.textFill == null; + if (isInside) { + if (textFillNotSet) { + out.textFill = txCfg.insideFill || '#fff'; + !out.textStroke && txCfg.insideStroke && (out.textStroke = txCfg.insideStroke); + !out.textStroke && (out.textStroke = hostFill); + out.textStrokeWidth == null && (out.textStrokeWidth = 2); + } + } + else { + if (textFillNotSet) { + out.textFill = txCfg.outsideFill || hostFill; + } + !out.textStroke && txCfg.outsideStroke && (out.textStroke = txCfg.outsideStroke); + } + + out.text = txStl.text; + out.rich = txStl.rich; + + each(txStl.rich, function (richItem) { + convertToEC4RichItem(richItem as Dictionary, richItem); + }); + + return out; +} + +function convertToEC4RichItem(out: Dictionary, richItem: TextStylePropsPart) { + if (!richItem) { + return; + } + + hasOwn(richItem, 'fill') && (out.textFill = richItem.fill); + hasOwn(richItem, 'stroke') && (out.textStroke = richItem.fill); + + hasOwn(richItem, 'lineWidth') && (out.textStrokeWidth = richItem.lineWidth); + hasOwn(richItem, 'font') && (out.textStrokeWidth = richItem.font); + hasOwn(richItem, 'fontStyle') && (out.fontStyle = richItem.fontStyle); + hasOwn(richItem, 'fontWeight') && (out.fontWeight = richItem.fontWeight); + hasOwn(richItem, 'fontSize') && (out.fontSize = richItem.fontSize); + hasOwn(richItem, 'fontFamily') && (out.fontFamily = richItem.fontFamily); + + hasOwn(richItem, 'align') && (out.textAlign = richItem.align); + hasOwn(richItem, 'verticalAlign') && (out.textVerticalAlign = richItem.verticalAlign); + hasOwn(richItem, 'lineHeight') && (out.textLineHeight = richItem.lineHeight); + hasOwn(richItem, 'width') && (out.textWidth = richItem.width); + hasOwn(richItem, 'height') && (out.textHeight = richItem.height); + + hasOwn(richItem, 'backgroundColor') && (out.textBackgroundColor = richItem.backgroundColor); + hasOwn(richItem, 'padding') && (out.textPadding = richItem.padding); + hasOwn(richItem, 'borderColor') && (out.textBorderColor = richItem.borderColor); + hasOwn(richItem, 'borderWidth') && (out.textBorderWidth = richItem.borderWidth); + hasOwn(richItem, 'borderRadius') && (out.textBorderRadius = richItem.borderRadius); + + hasOwn(richItem, 'shadowColor') && (out.textBoxShadowColor = richItem.shadowColor); + hasOwn(richItem, 'shadowBlur') && (out.textBoxShadowBlur = richItem.shadowBlur); + hasOwn(richItem, 'shadowOffsetX') && (out.textBoxShadowOffsetX = richItem.shadowOffsetX); + hasOwn(richItem, 'shadowOffsetY') && (out.textBoxShadowOffsetY = richItem.shadowOffsetY); + + hasOwn(richItem, 'textShadowColor') && (out.textShadowColor = richItem.textShadowColor); + hasOwn(richItem, 'textShadowBlur') && (out.textShadowBlur = richItem.textShadowBlur); + hasOwn(richItem, 'textShadowOffsetX') && (out.textShadowOffsetX = richItem.textShadowOffsetX); + hasOwn(richItem, 'textShadowOffsetY') && (out.textShadowOffsetY = richItem.textShadowOffsetY); +} + +export function warnDeprecated(deprecated: string, insteadApproach: string): void { + if (__DEV__) { + const key = deprecated + '^_^' + insteadApproach; + if (!deprecatedLogs[key]) { + console.warn(`DEPRECATED: "${deprecated}" has been deprecated. ${insteadApproach}`); + deprecatedLogs[key] = true; + } + } +} diff --git a/src/util/types.ts b/src/util/types.ts index 07f773e771..b74d08caf3 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -106,6 +106,7 @@ export interface ECElement extends Element { }; highDownSilentOnTouch?: boolean; onStateChange?: (fromState: 'normal' | 'emphasis', toState: 'normal' | 'emphasis') => void; + z2EmphasisLift?: number; } export interface DataHost { @@ -422,6 +423,7 @@ export type ModelOption = any; export type ThemeOption = Dictionary; export type DisplayState = 'normal' | 'emphasis'; +export type DisplayStateNonNormal = 'emphasis'; export type DisplayStateHostOption = { emphasis?: Dictionary, [key: string]: any diff --git a/test/circle-packing-with-d3.compat.html b/test/circle-packing-with-d3.compat.html new file mode 100644 index 0000000000..486b6e36a4 --- /dev/null +++ b/test/circle-packing-with-d3.compat.html @@ -0,0 +1,170 @@ + + + + + + + + + +
+ + + + + diff --git a/test/circle-packing-with-d3.html b/test/circle-packing-with-d3.html index 486b6e36a4..06a77ecc8b 100644 --- a/test/circle-packing-with-d3.html +++ b/test/circle-packing-with-d3.html @@ -45,6 +45,7 @@ + diff --git a/test/custom-feature.html b/test/custom-feature.html index 7ec76969af..03457ed843 100644 --- a/test/custom-feature.html +++ b/test/custom-feature.html @@ -85,6 +85,7 @@ style: { fill: 'red', text: 'dataIndex: ' + params.dataIndex, + textFill: '#000', textStroke: '#fff', textStrokeWidth: 1 } diff --git a/test/custom-text-content.html b/test/custom-text-content.html new file mode 100644 index 0000000000..d999acd49e --- /dev/null +++ b/test/custom-text-content.html @@ -0,0 +1,1193 @@ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/hoverStyle.html b/test/hoverStyle.html index 6a5bca7bbc..245068c8a2 100644 --- a/test/hoverStyle.html +++ b/test/hoverStyle.html @@ -680,7 +680,8 @@ // silent: true, label: { show: true, - silent: true, + // silent: true, + position: 'top' }, itemStyle: { color: 'green', From 88c51a08a6c4ed323b0eb93e7f8ac43e3db2151a Mon Sep 17 00:00:00 2001 From: 100pah Date: Wed, 13 May 2020 02:12:08 +0800 Subject: [PATCH 02/14] fix: fix custom series api.size in polor. --- src/coord/polar/prepareCustom.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coord/polar/prepareCustom.ts b/src/coord/polar/prepareCustom.ts index 5a69988752..1907f7f878 100644 --- a/src/coord/polar/prepareCustom.ts +++ b/src/coord/polar/prepareCustom.ts @@ -24,6 +24,7 @@ import RadiusAxis from './RadiusAxis'; function dataToCoordSize(this: Polar, dataSize: number[], dataItem: number[]) { // dataItem is necessary in log axis. + dataItem = dataItem || [0, 0]; return zrUtil.map(['Radius', 'Angle'], function (dim, dimIdx) { const getterName = 'get' + dim + 'Axis' as 'getAngleAxis'| 'getRadiusAxis'; // TODO: TYPE Check Angle Axis From c56e2dcd1e553780b91c6a004c1a06b770f082bf Mon Sep 17 00:00:00 2001 From: 100pah Date: Wed, 13 May 2020 04:08:33 +0800 Subject: [PATCH 03/14] feature: add duration animation for custom series. --- src/chart/custom.ts | 54 ++++- test/custom-feature.html | 442 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 494 insertions(+), 2 deletions(-) diff --git a/src/chart/custom.ts b/src/chart/custom.ts index 3ed15b903b..a58633d759 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -84,6 +84,8 @@ const inner = makeInner<{ customImagePath: CustomImageOption['style']['image']; customText: string; txConZ2Set: number; + orginalDuring: Element['updateDuringAnimation']; + customDuring: CustomZRPathOption['during']; }, Element>(); type CustomExtraElementInfo = Dictionary; @@ -103,6 +105,8 @@ interface CustomBaseElementOption extends Partial { } +interface CustomDuringElProps extends Partial> { + shape?: PathProps['shape']; +} interface CustomSVGPathOption extends CustomDisplayableOption { type: 'path'; shape?: { @@ -277,7 +284,7 @@ const Z2_SPECIFIED_BIT = { emphasis: 1 } as const; - +const tmpDuringElProps = {} as CustomDuringElProps; export type PrepareCustomInfo = (coordSys: CoordinateSystem) => { @@ -640,6 +647,19 @@ function updateElNormal( zrUtil.hasOwn(elOption, 'silent') && (el.silent = elOption.silent); zrUtil.hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore); + const customDuringMounted = el.updateDuringAnimation === elUpdateDuringAnimation; + if (elOption.during) { + const innerEl = inner(el); + if (!customDuringMounted) { + innerEl.orginalDuring = el.updateDuringAnimation; + el.updateDuringAnimation = elUpdateDuringAnimation; + } + innerEl.customDuring = elOption.during; + } + else if (customDuringMounted) { + el.updateDuringAnimation = inner(el).orginalDuring; + } + if (!isTextContent) { // `elOption.info` enables user to mount some info on // elements and use them in event handlers. @@ -650,6 +670,38 @@ function updateElNormal( el.markRedraw(); } +function elUpdateDuringAnimation(this: graphicUtil.Path, key: string): void { + const innerEl = inner(this); + // FIXME `this.markRedraw();` directly ? + innerEl.orginalDuring.call(this, key); + const customDuring = innerEl.customDuring; + + // Only provide these props. Usually other props do not need to be + // changed in animation during. + // Do not give `this` to user util really needed in future. + // Props in `shape` can be modified directly in the during callback. + tmpDuringElProps.shape = this.shape; + tmpDuringElProps.x = this.x; + tmpDuringElProps.y = this.y; + tmpDuringElProps.scaleX = this.scaleX; + tmpDuringElProps.scaleX = this.scaleY; + tmpDuringElProps.originX = this.originX; + tmpDuringElProps.originY = this.originY; + tmpDuringElProps.rotation = this.rotation; + + customDuring(tmpDuringElProps); + + tmpDuringElProps.shape !== this.shape && (this.shape = tmpDuringElProps.shape); + // Consider prop on prototype. + tmpDuringElProps.x !== this.x && (this.x = tmpDuringElProps.x); + tmpDuringElProps.y !== this.y && (this.y = tmpDuringElProps.y); + tmpDuringElProps.scaleX !== this.scaleX && (this.scaleX = tmpDuringElProps.scaleX); + tmpDuringElProps.scaleY !== this.scaleY && (this.scaleY = tmpDuringElProps.scaleY); + tmpDuringElProps.originX !== this.originX && (this.originX = tmpDuringElProps.originX); + tmpDuringElProps.originY !== this.originY && (this.originY = tmpDuringElProps.originY); + tmpDuringElProps.rotation !== this.rotation && (this.rotation = tmpDuringElProps.rotation); +} + function updateElOnState( state: DisplayStateNonNormal, el: Element, diff --git a/test/custom-feature.html b/test/custom-feature.html index 03457ed843..56e106ee57 100644 --- a/test/custom-feature.html +++ b/test/custom-feature.html @@ -38,7 +38,10 @@
- +
+ +
+
+ + + + + + + + + + + + + + + \ No newline at end of file From 13093174904971bcacb8ab2bda079df079b0195d Mon Sep 17 00:00:00 2001 From: 100pah Date: Tue, 19 May 2020 04:45:26 +0800 Subject: [PATCH 04/14] feature: support text animation on custom series (via in `during`) --- src/chart/custom.ts | 88 +++++++++++------- test/custom-feature.html | 190 ++++++++++++++++++++++++--------------- 2 files changed, 174 insertions(+), 104 deletions(-) diff --git a/src/chart/custom.ts b/src/chart/custom.ts index a58633d759..cfb9587fcc 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -82,7 +82,7 @@ const inner = makeInner<{ customPathData: string; customGraphicType: string; customImagePath: CustomImageOption['style']['image']; - customText: string; + // customText: string; txConZ2Set: number; orginalDuring: Element['updateDuringAnimation']; customDuring: CustomZRPathOption['during']; @@ -126,6 +126,7 @@ interface CustomGroupOption extends CustomBaseElementOption { type: 'group'; width?: number; height?: number; + // @deprecated diffChildrenByName?: boolean; children: CustomElementOption[]; $mergeChildren: false | 'byName' | 'byIndex'; @@ -134,6 +135,7 @@ interface CustomZRPathOption extends CustomDisplayableOption, Pick> { shape?: PathProps['shape']; + style?: { text: string }; } interface CustomSVGPathOption extends CustomDisplayableOption { type: 'path'; @@ -284,8 +286,7 @@ const Z2_SPECIFIED_BIT = { emphasis: 1 } as const; -const tmpDuringElProps = {} as CustomDuringElProps; - +const tmpDuringElProps = { style: {} } as CustomDuringElProps; export type PrepareCustomInfo = (coordSys: CoordinateSystem) => { coordSys: CustomSeriesRenderItemParamsCoordSys; @@ -507,7 +508,7 @@ function createEl(elOption: CustomElementOption): Element { } else if (graphicType === 'text') { el = new graphicUtil.Text({}); - inner(el).customText = (elOption.style as TextStyleProps).text; + // inner(el).customText = (elOption.style as TextStyleProps).text; } else if (graphicType === 'group') { el = new graphicUtil.Group(); @@ -667,39 +668,58 @@ function updateElNormal( zrUtil.hasOwn(elOption, 'info') && (inner(el).info = elOption.info); } - el.markRedraw(); + styleOpt ? el.dirty() : el.markRedraw(); } -function elUpdateDuringAnimation(this: graphicUtil.Path, key: string): void { +function elUpdateDuringAnimation(this: Element, key: string): void { const innerEl = inner(this); // FIXME `this.markRedraw();` directly ? innerEl.orginalDuring.call(this, key); const customDuring = innerEl.customDuring; + const thisPath = this as graphicUtil.Path; + const thisText = this as graphicUtil.Text; + let dirtyStyle = false; // Only provide these props. Usually other props do not need to be // changed in animation during. // Do not give `this` to user util really needed in future. // Props in `shape` can be modified directly in the during callback. - tmpDuringElProps.shape = this.shape; - tmpDuringElProps.x = this.x; - tmpDuringElProps.y = this.y; - tmpDuringElProps.scaleX = this.scaleX; - tmpDuringElProps.scaleX = this.scaleY; - tmpDuringElProps.originX = this.originX; - tmpDuringElProps.originY = this.originY; - tmpDuringElProps.rotation = this.rotation; + const shapeCurr = tmpDuringElProps.shape = thisPath.shape; + const xCurr = tmpDuringElProps.x = this.x; + const yCurr = tmpDuringElProps.y = this.y; + const scaleXCurr = tmpDuringElProps.scaleX = this.scaleX; + const scaleYCurr = tmpDuringElProps.scaleY = this.scaleY; + const originXCurr = tmpDuringElProps.originX = this.originX; + const originYCurr = tmpDuringElProps.originY = this.originY; + const rotationCurr = tmpDuringElProps.rotation = this.rotation; + + // PENDING: + // Do not expose other style in case that is not stable. + const isText = this.type === 'text'; + const textCurr = tmpDuringElProps.style.text = isText ? thisText.style.text : null; customDuring(tmpDuringElProps); - tmpDuringElProps.shape !== this.shape && (this.shape = tmpDuringElProps.shape); + tmpDuringElProps.shape !== shapeCurr && (thisPath.shape = tmpDuringElProps.shape); // Consider prop on prototype. - tmpDuringElProps.x !== this.x && (this.x = tmpDuringElProps.x); - tmpDuringElProps.y !== this.y && (this.y = tmpDuringElProps.y); - tmpDuringElProps.scaleX !== this.scaleX && (this.scaleX = tmpDuringElProps.scaleX); - tmpDuringElProps.scaleY !== this.scaleY && (this.scaleY = tmpDuringElProps.scaleY); - tmpDuringElProps.originX !== this.originX && (this.originX = tmpDuringElProps.originX); - tmpDuringElProps.originY !== this.originY && (this.originY = tmpDuringElProps.originY); - tmpDuringElProps.rotation !== this.rotation && (this.rotation = tmpDuringElProps.rotation); + tmpDuringElProps.x !== xCurr && (this.x = tmpDuringElProps.x); + tmpDuringElProps.y !== yCurr && (this.y = tmpDuringElProps.y); + tmpDuringElProps.scaleX !== scaleXCurr && (this.scaleX = tmpDuringElProps.scaleX); + tmpDuringElProps.scaleY !== scaleYCurr && (this.scaleY = tmpDuringElProps.scaleY); + tmpDuringElProps.originX !== originXCurr && (this.originX = tmpDuringElProps.originX); + tmpDuringElProps.originY !== originYCurr && (this.originY = tmpDuringElProps.originY); + tmpDuringElProps.rotation !== rotationCurr && (this.rotation = tmpDuringElProps.rotation); + + if (isText) { + const currTmpStl = tmpDuringElProps.style; + currTmpStl && currTmpStl.text !== textCurr && (thisText.style.text = currTmpStl.text, dirtyStyle = true); + } + + dirtyStyle && this.dirty(); + // markRedraw() will be called by default. + + // FIXME: if in future meet the case that some prop will be both modified in `during` and `state`, + // consider the issue that the prop might be incorrect when return to "normal" state. } function updateElOnState( @@ -1193,6 +1213,7 @@ function doCreateOrUpdate( const elOptionType = elOption.type; const elOptionShape = (elOption as CustomZRPathOption).shape; const elOptionStyle = elOption.style; + let toBeReplacedIdx = -1; if (el) { const elInner = inner(el); @@ -1210,13 +1231,14 @@ function doCreateOrUpdate( && zrUtil.hasOwn(elOptionStyle, 'image') && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath ) - // FIXME test and remove this restriction? - || (elOptionType === 'text' - && zrUtil.hasOwn(elOptionStyle, 'text') - && (elOptionStyle as TextStyleProps).text !== elInner.customText - ) + // // FIXME test and remove this restriction? + // || (elOptionType === 'text' + // && zrUtil.hasOwn(elOptionStyle, 'text') + // && (elOptionStyle as TextStyleProps).text !== elInner.customText + // ) ) { - group.remove(el); + // Should keep at the original index, otherwise "merge by index" will be incorrect. + toBeReplacedIdx = group.childrenRef().indexOf(el); el = null; } } @@ -1254,8 +1276,12 @@ function doCreateOrUpdate( ); } - // Always add whatever already added to ensure sequence. - group.add(el); + if (toBeReplacedIdx >= 0) { + group.replaceAt(el, toBeReplacedIdx); + } + else { + group.add(el); + } return el; } @@ -1316,7 +1342,7 @@ function doCreateOrUpdateAttachedTx( const txConStlOptEmphasis = retrieveStyleOptionOnState(txConOptNormal, txConOptEmphasis, EMPHASIS); updateElOnState(EMPHASIS, textContent, txConOptEmphasis, txConStlOptEmphasis, null, false, true); - textContent.markRedraw(); + txConStlOptNormal ? textContent.dirty() : textContent.markRedraw(); } } } diff --git a/test/custom-feature.html b/test/custom-feature.html index 56e106ee57..dabf8b1d04 100644 --- a/test/custom-feature.html +++ b/test/custom-feature.html @@ -636,7 +636,7 @@ 'echarts'/*, 'map/js/china' */ ], function (echarts) { var animationDuration = 5000; - var animationDurationUpdate = 4000; + var animationDurationUpdate = 7000; var animationEasingUpdate = 'elasticOut'; var angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces']; var angleRoundValue = angleLabel.length; @@ -644,85 +644,148 @@ var angleStep = angleRoundValue / 90; var barWidthValue = 0.4; var radiusStep = 4; - var colors = { - 'A': { stroke: 'green', fill: 'rgba(0,152,0,0.6)' }, - 'B': { stroke: 'red', fill: 'rgba(152,0,0,0.6)' }, - 'C': { stroke: 'blue', fill: 'rgba(0,0, 152,0.6)' }, - }; - var allData = [[ - [[1, 3, 'A']], - [[2, 6, 'B']], - [[3, 9, 'C']], - ], [ - [[1, 12, 'A']], - [[2, 16, 'B']], - [[3, 14, 'C']], - ], [ - [[1, 17, 'A']], - [[2, 22, 'B']], - [[3, 19, 'C']], - ]]; - var currentDataIndex = 0; + var colors = [ + { border: 'green', inner: 'rgba(0,152,0,0.6)' }, + { border: 'red', inner: 'rgba(152,0,0,0.6)' }, + { border: 'blue', inner: 'rgba(0,0, 152,0.6)' }, + ]; + var currentDataIndex = 0; + var datasourceList = [ + [[3, 6, 9]], + [[12, 16, 14]], + [[17, 22, 19]], + ]; + var barValOnRadiusList = [1, 2, 3]; + + // PENDING: + // The radius max should be fixed rather than change dynamically. + // If need to support dynamic coord sys while animation: + // (A) The `api.coord` should be able to accept a customized extent and + // return value on the middle state. + // or (B) Use "data interpolate". function getMaxRadius() { var radius = 0; - for (var j = 0; j < allData.length; j++) { - var data = allData[j]; - for (var i = 0; i < data.length; i++) { - radius = Math.max(radius, getSpiralValueRadius(data[i][0][0], data[i][0][1])); + for (var k = 0; k < barValOnRadiusList.length; k++) { + for (var i = 0; i < datasourceList.length; i++) { + var row = datasourceList[i][0]; + for (var j = 0; j < row.length; j++) { + var valOnAngle = row[j]; + radius = Math.max( + radius, + getSpiralValueRadius(barValOnRadiusList[k], valOnAngle) + ); + } } } return Math.ceil(radius * 1.2); } - function getSpiralValueRadius(valRadius, valAngle) { - return valRadius + radiusStep * (valAngle / angleRoundValue); + function getSpiralValueRadius(valOnRadius, valOnAngle) { + return valOnRadius + radiusStep * (valOnAngle / angleRoundValue); + } + + function addShapes(api, children, valOnRadius, valOnAngle, color) { + addPolygon(api, children, valOnRadius, valOnAngle, color); + addLabel(api, children, valOnRadius, valOnAngle, color); } - function makeShapePoints(api, valueRadius, valueAngle) { + function addPolygon(api, children, valOnRadius, valOnAngle, color) { + children.push({ + type: 'polygon', + shape: { + points: makeShapePoints(api, valOnRadius, valOnAngle), + valOnAngle: valOnAngle + }, + style: { + lineWidth: 1, + fill: color.inner, + stroke: color.border + }, + during: function (elProps) { + elProps.shape.points = makeShapePoints( + api, valOnRadius, elProps.shape.valOnAngle + ); + } + }); + } + + function makeShapePoints(api, valOnRadius, valOnAngle) { var points = []; - for (var iAngleVal = 0, end = valueAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) { - iAngleVal > valueAngle && (iAngleVal = valueAngle); - var iRadiusVal = getSpiralValueRadius(valueRadius - barWidthValue, iAngleVal); + for (var iAngleVal = 0, end = valOnAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) { + iAngleVal > valOnAngle && (iAngleVal = valOnAngle); + var iRadiusVal = getSpiralValueRadius(valOnRadius - barWidthValue, iAngleVal); var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2); points.push(point); } - for (var iAngleVal = valueAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) { + for (var iAngleVal = valOnAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) { iAngleVal < 0 && (iAngleVal = 0); - var iRadiusVal = getSpiralValueRadius(valueRadius + barWidthValue, iAngleVal); + var iRadiusVal = getSpiralValueRadius(valOnRadius + barWidthValue, iAngleVal); var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2); points.push(point); } return points; } - function renderItem(params, api) { - var valueRadius = api.value(0); - var valueAngle = api.value(1); - var name = api.value(2); - return { - type: 'polygon', + function addLabel(api, children, valOnRadius, valOnAngle, color) { + var point = makeLabelPosition(api, valOnRadius, valOnAngle); + children.push({ + type: 'text', + x: point[0], + y: point[1], shape: { - points: makeShapePoints(api, valueRadius, valueAngle), - valueAngle: valueAngle + valOnAngle: valOnAngle }, style: { - lineWidth: 1, - fill: colors[name].fill, - stroke: colors[name].stroke + text: getText(valOnAngle), + fill: color.inner, + stroke: '#fff', + lineWidth: 3, + fontSize: 16, + align: 'center', + verticalAlign: 'middle' }, + z2: 50, during: function (elProps) { - elProps.shape.points = makeShapePoints( - api, valueRadius, elProps.shape.valueAngle - ); + var iValOnAngle = elProps.shape.valOnAngle; + var point = makeLabelPosition(api, valOnRadius, iValOnAngle); + elProps.x = point[0]; + elProps.y = point[1]; + elProps.style.text = getText(iValOnAngle); } + }); + + function getText(iValOnAngle) { + return (iValOnAngle / angleRoundValue * 100).toFixed(0) + '%' + } + } + + function makeLabelPosition(api, valOnRadius, valOnAngle) { + var iRadiusVal = getSpiralValueRadius(valOnRadius, valOnAngle); + return api.coord([iRadiusVal, valOnAngle + 1 / iRadiusVal / (2 * Math.PI) * angleRoundValue]); + } + + function renderItem(params, api) { + var children = []; + + addShapes(api, children, barValOnRadiusList[0], api.value(0), colors[0]); + addShapes(api, children, barValOnRadiusList[1], api.value(1), colors[1]); + addShapes(api, children, barValOnRadiusList[2], api.value(2), colors[2]); + + return { + type: 'group', + children: children }; } var option = { + // animation: false, animationDuration: animationDuration, animationDurationUpdate: animationDurationUpdate, animationEasingUpdate: animationEasingUpdate, + dataset: { + source: datasourceList[currentDataIndex] + }, angleAxis: { type: 'value', // splitLine: { show: false }, @@ -745,49 +808,30 @@ min: 0, max: getMaxRadius() }, - polar: { - }, + polar: {}, tooltip: {}, series: [{ type: 'custom', - name: 'A', - coordinateSystem: 'polar', - renderItem: renderItem, - data: allData[currentDataIndex][0] - }, { - type: 'custom', - name: 'B', coordinateSystem: 'polar', - renderItem: renderItem, - data: allData[currentDataIndex][1] - }, { - type: 'custom', - name: 'C', - coordinateSystem: 'polar', - renderItem: renderItem, - data: allData[currentDataIndex][2] + renderItem: renderItem }] }; var chart = testHelper.create(echarts, 'spiral2', { title: [ - 'animation: ', + 'polygon animation should be corrent. (coordSys extent is fixed)', ], option: option, buttons: [{ text: 'next', onclick: function () { currentDataIndex++; - currentDataIndex >= allData.length && (currentDataIndex = 0); + currentDataIndex >= datasourceList.length && (currentDataIndex = 0); chart.setOption({ - series: [{ - data: allData[currentDataIndex][0] - }, { - data: allData[currentDataIndex][1] - }, { - data: allData[currentDataIndex][2] - }] - }) + dataset: { + source: datasourceList[currentDataIndex] + } + }); } }, { text: 'enable animation', From a631dba659430989d131a2030d8f70c361173175 Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 21 May 2020 22:34:59 +0800 Subject: [PATCH 05/14] feature: support axis label/tick animation on polar radius axis, the same as cartesian axis. --- src/component/axis/RadiusAxisView.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/component/axis/RadiusAxisView.ts b/src/component/axis/RadiusAxisView.ts index e40abeb908..439953f479 100644 --- a/src/component/axis/RadiusAxisView.ts +++ b/src/component/axis/RadiusAxisView.ts @@ -42,11 +42,18 @@ class RadiusAxisView extends AxisView { axisPointerClass = 'PolarAxisPointer'; + private _axisGroup: graphic.Group; + render(radiusAxisModel: RadiusAxisModel, ecModel: GlobalModel) { this.group.removeAll(); if (!radiusAxisModel.get('show')) { return; } + + const oldAxisGroup = this._axisGroup; + const newAxisGroup = this._axisGroup = new graphic.Group(); + this.group.add(newAxisGroup); + const radiusAxis = radiusAxisModel.axis; const polar = radiusAxis.polar; const angleAxis = polar.getAngleAxis(); @@ -58,7 +65,9 @@ class RadiusAxisView extends AxisView { const layout = layoutAxis(polar, radiusAxisModel, axisAngle); const axisBuilder = new AxisBuilder(radiusAxisModel, layout); zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder); - this.group.add(axisBuilder.getGroup()); + newAxisGroup.add(axisBuilder.getGroup()); + + graphic.groupTransition(oldAxisGroup, newAxisGroup, radiusAxisModel); zrUtil.each(selfBuilderAttrs, function (name) { if (radiusAxisModel.get([name, 'show']) && !radiusAxis.scale.isBlank()) { From d0315f25e9a4e6b8bcf86b76b2d5eb3e8a47c61d Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 21 May 2020 22:43:10 +0800 Subject: [PATCH 06/14] chore: tweak and make better example for tutorial. --- src/chart/custom.ts | 9 +- test/custom-feature.html | 483 --------------------------- test/custom-transition.html | 627 ++++++++++++++++++++++++++++++++++++ 3 files changed, 633 insertions(+), 486 deletions(-) create mode 100644 test/custom-transition.html diff --git a/src/chart/custom.ts b/src/chart/custom.ts index cfb9587fcc..7af3ec0932 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -286,7 +286,8 @@ const Z2_SPECIFIED_BIT = { emphasis: 1 } as const; -const tmpDuringElProps = { style: {} } as CustomDuringElProps; +const tmpDuringStyle = {} as CustomDuringElProps['style']; +const tmpDuringElProps = {} as CustomDuringElProps; export type PrepareCustomInfo = (coordSys: CoordinateSystem) => { coordSys: CustomSeriesRenderItemParamsCoordSys; @@ -696,7 +697,9 @@ function elUpdateDuringAnimation(this: Element, key: string): void { // PENDING: // Do not expose other style in case that is not stable. const isText = this.type === 'text'; - const textCurr = tmpDuringElProps.style.text = isText ? thisText.style.text : null; + // Always assign in case that user modify `.style`. + tmpDuringElProps.style = tmpDuringStyle; + const textCurr = tmpDuringStyle.text = isText ? thisText.style.text : null; customDuring(tmpDuringElProps); @@ -711,7 +714,7 @@ function elUpdateDuringAnimation(this: Element, key: string): void { tmpDuringElProps.rotation !== rotationCurr && (this.rotation = tmpDuringElProps.rotation); if (isText) { - const currTmpStl = tmpDuringElProps.style; + const currTmpStl = tmpDuringElProps.style; // Allow user modify `.style`. currTmpStl && currTmpStl.text !== textCurr && (thisText.style.text = currTmpStl.text, dirtyStyle = true); } diff --git a/test/custom-feature.html b/test/custom-feature.html index dabf8b1d04..cee7c5370e 100644 --- a/test/custom-feature.html +++ b/test/custom-feature.html @@ -38,10 +38,6 @@
-
- -
-
- - - - - - - - - - - - - diff --git a/test/custom-transition.html b/test/custom-transition.html new file mode 100644 index 0000000000..77a6aaba04 --- /dev/null +++ b/test/custom-transition.html @@ -0,0 +1,627 @@ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 1dffebc9bf846f8ea0d75fe97407e8d12a261eab Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 28 May 2020 17:26:56 +0800 Subject: [PATCH 07/14] feature: support clipPath and clipPath animation in custom series. --- src/chart/custom.ts | 124 +++++++++++++------ test/custom-transition.html | 241 +++++++++++++++++++++++++++++++++++- 2 files changed, 323 insertions(+), 42 deletions(-) diff --git a/src/chart/custom.ts b/src/chart/custom.ts index 7af3ec0932..2bb85c95bd 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -105,6 +105,8 @@ interface CustomBaseElementOption extends Partial ): Element { - el = doCreateOrUpdate(el, dataIndex, elOption, seriesModel, group, data, true); + el = doCreateOrUpdate(el, dataIndex, elOption, seriesModel, group, true); el && data.setItemGraphicEl(dataIndex, el); return el; @@ -1192,7 +1194,6 @@ function doCreateOrUpdate( elOption: CustomElementOption, seriesModel: CustomSeriesModel, group: ViewRootGroup, - data: List, isRoot: boolean ): Element { @@ -1213,37 +1214,12 @@ function doCreateOrUpdate( } elOption = elOption || {} as CustomElementOption; - const elOptionType = elOption.type; - const elOptionShape = (elOption as CustomZRPathOption).shape; - const elOptionStyle = elOption.style; let toBeReplacedIdx = -1; - if (el) { - const elInner = inner(el); - if ( - // || elOption.$merge === false - // If `elOptionType` is `null`, follow the merge principle. - (elOptionType != null - && elOptionType !== elInner.customGraphicType - ) - || (elOptionType === 'path' - && hasOwnPathData(elOptionShape) - && getPathData(elOptionShape) !== elInner.customPathData - ) - || (elOptionType === 'image' - && zrUtil.hasOwn(elOptionStyle, 'image') - && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath - ) - // // FIXME test and remove this restriction? - // || (elOptionType === 'text' - // && zrUtil.hasOwn(elOptionStyle, 'text') - // && (elOptionStyle as TextStyleProps).text !== elInner.customText - // ) - ) { - // Should keep at the original index, otherwise "merge by index" will be incorrect. - toBeReplacedIdx = group.childrenRef().indexOf(el); - el = null; - } + if (el && doesElNeedRecreate(el, elOption)) { + // Should keep at the original index, otherwise "merge by index" will be incorrect. + toBeReplacedIdx = group.childrenRef().indexOf(el); + el = null; } const isInit = !el; @@ -1252,6 +1228,7 @@ function doCreateOrUpdate( el = createEl(elOption); } else { + // FIMXE:NEXT unified clearState? // If in some case the performance issue arised, consider // do not clearState but update cached normal state directly. el.clearStates(); @@ -1265,6 +1242,10 @@ function doCreateOrUpdate( el, dataIndex, elOption, seriesModel, isInit, attachedTxInfoTmp ); + doCreateOrUpdateClipPath( + el, dataIndex, elOption, seriesModel, isInit + ); + const stateOptEmphasis = retrieveStateOption(elOption, EMPHASIS); const styleOptEmphasis = retrieveStyleOptionOnState(elOption, stateOptEmphasis, EMPHASIS); @@ -1273,9 +1254,9 @@ function doCreateOrUpdate( updateZ(el, elOption, seriesModel, attachedTxInfoTmp); - if (elOptionType === 'group') { + if (elOption.type === 'group') { mergeChildren( - el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, data + el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel ); } @@ -1289,6 +1270,72 @@ function doCreateOrUpdate( return el; } +// `el` must not be null/undefined. +function doesElNeedRecreate(el: Element, elOption: CustomElementOption): boolean { + const elInner = inner(el); + const elOptionType = elOption.type; + const elOptionShape = (elOption as CustomZRPathOption).shape; + const elOptionStyle = elOption.style; + return ( + // || elOption.$merge === false + // If `elOptionType` is `null`, follow the merge principle. + (elOptionType != null + && elOptionType !== elInner.customGraphicType + ) + || (elOptionType === 'path' + && hasOwnPathData(elOptionShape) + && getPathData(elOptionShape) !== elInner.customPathData + ) + || (elOptionType === 'image' + && zrUtil.hasOwn(elOptionStyle, 'image') + && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath + ) + // // FIXME test and remove this restriction? + // || (elOptionType === 'text' + // && zrUtil.hasOwn(elOptionStyle, 'text') + // && (elOptionStyle as TextStyleProps).text !== elInner.customText + // ) + ); +} + +function doCreateOrUpdateClipPath( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + isInit: boolean +): void { + // Based on the "merge" principle, if no clipPath provided, + // do nothing. The exists clip will be totally removed only if + // `el.clipPath` is `false`. Otherwise it will be merged/replaced. + const clipPathOpt = elOption.clipPath; + if (clipPathOpt === false) { + if (el && el.getClipPath()) { + el.removeClipPath(); + } + } + else if (clipPathOpt) { + let clipPath = el.getClipPath(); + if (clipPath && doesElNeedRecreate(clipPath, clipPathOpt)) { + clipPath = null; + } + if (!clipPath) { + clipPath = createEl(clipPathOpt) as graphicUtil.Path; + if (__DEV__) { + zrUtil.assert( + clipPath instanceof graphicUtil.Path, + 'Only any type of `path` can be used in `clipPath`, rather than ' + clipPath.type + '.' + ); + } + el.setClipPath(clipPath); + } + updateElNormal( + clipPath, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false + ); + } + // If not define `clipPath` in option, do nothing unnecessary. +} + function doCreateOrUpdateAttachedTx( el: Element, dataIndex: number, @@ -1440,8 +1487,7 @@ function mergeChildren( el: graphicUtil.Group, dataIndex: number, elOption: CustomGroupOption, - seriesModel: CustomSeriesModel, - data: List + seriesModel: CustomSeriesModel ): void { const newChildren = elOption.children; @@ -1462,8 +1508,7 @@ function mergeChildren( newChildren: newChildren || [], dataIndex: dataIndex, seriesModel: seriesModel, - group: el, - data: data + group: el }); return; } @@ -1480,7 +1525,6 @@ function mergeChildren( newChildren[index], seriesModel, el, - data, false ); } @@ -1497,8 +1541,7 @@ type DiffGroupContext = { newChildren: CustomElementOption[], dataIndex: number, seriesModel: CustomSeriesModel, - group: graphicUtil.Group, - data: List + group: graphicUtil.Group }; function diffGroupChildren(context: DiffGroupContext) { (new DataDiffer( @@ -1534,7 +1577,6 @@ function processAddUpdate( childOption, context.seriesModel, context.group, - context.data, false ); } diff --git a/test/custom-transition.html b/test/custom-transition.html index 77a6aaba04..2d8c718e42 100644 --- a/test/custom-transition.html +++ b/test/custom-transition.html @@ -28,6 +28,7 @@ + @@ -38,7 +39,8 @@
-
+
+ @@ -623,5 +625,242 @@ + + + + + + + + + + + + + + + + \ No newline at end of file From dfad53d54fc1ca02d1e605b114b087fda6d7e623 Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 4 Jun 2020 11:39:59 +0800 Subject: [PATCH 08/14] feature: enable "enterFrom" "leaveTo" "transition" animation setting in custom series. --- src/chart/custom.ts | 513 +++++++++++++----- src/component/visualMap/PiecewiseModel.ts | 2 +- src/util/graphic.ts | 51 +- test/custom-transition-texture.js | 1 + test/custom-transition.html | 613 ++++++++++++++++++---- 5 files changed, 926 insertions(+), 254 deletions(-) create mode 100644 test/custom-transition-texture.js diff --git a/src/chart/custom.ts b/src/chart/custom.ts index 2bb85c95bd..cfd1a1f1e5 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -19,7 +19,9 @@ import {__DEV__} from '../config'; -import * as zrUtil from 'zrender/src/core/util'; +import { + hasOwn, assert, isString, retrieve2, retrieve3, defaults, each, keys, isArrayLike +} from 'zrender/src/core/util'; import * as graphicUtil from '../util/graphic'; import {getDefaultLabel} from './helper/labelHelper'; import createListFromArray from './helper/createListFromArray'; @@ -57,7 +59,7 @@ import prepareCalendar from '../coord/calendar/prepareCustom'; import ComponentModel from '../model/Component'; import List, { DefaultDataVisual } from '../data/List'; import GlobalModel from '../model/Global'; -import { makeInner } from '../util/model'; +import { makeInner, normalizeToArray } from '../util/model'; import ExtensionAPI from '../ExtensionAPI'; import Displayable from 'zrender/src/graphic/Displayable'; import Axis2D from '../coord/cartesian/Axis2D'; @@ -75,6 +77,7 @@ import { } from '../util/styleCompat'; import Transformable from 'zrender/src/core/Transformable'; import { ItemStyleProps } from '../model/mixin/itemStyle'; +import { cloneValue } from 'zrender/src/animation/Animator'; const inner = makeInner<{ @@ -86,17 +89,38 @@ const inner = makeInner<{ txConZ2Set: number; orginalDuring: Element['updateDuringAnimation']; customDuring: CustomZRPathOption['during']; + leaveToProps: ElementProps }, Element>(); type CustomExtraElementInfo = Dictionary; -type TransformPropsX = 'x' | 'scaleX' | 'originX'; -type TransformPropsY = 'y' | 'scaleY' | 'originY'; -type TransformProps = TransformPropsX | TransformPropsY | 'rotation'; - +const TRANSFORM_PROPS = { + x: 1, + y: 1, + scaleX: 1, + scaleY: 1, + originX: 1, + originY: 1, + rotation: 1 +} as const; +type TransformProps = keyof typeof TRANSFORM_PROPS; + +type TransitionAnyProps = string | string[]; +type TransitionTransformProps = TransformProps | TransformProps[]; +// Do not declare "Dictionary" in TransitionAnyOption to restrict the type check. +type TransitionAnyOption = { + $transition?: TransitionAnyProps; + $enterFrom?: Dictionary; + $leaveTo?: Dictionary; +}; +type TransitionTransformOption = { + $transition?: TransitionTransformProps; + $enterFrom?: Dictionary; + $leaveTo?: Dictionary; +}; interface CustomBaseElementOption extends Partial> { +>>, TransitionTransformOption { // element type, mandatory. type: string; id?: string; @@ -107,13 +131,15 @@ interface CustomBaseElementOption extends Partial> { - style?: ZRStyleProps; + style?: ZRStyleProps & TransitionAnyOption; // `false` means remove emphasis trigger. styleEmphasis?: ZRStyleProps | false; emphasis?: CustomDisplayableOptionOnState; @@ -122,7 +148,7 @@ interface CustomDisplayableOptionOnState extends Partial> { // `false` means remove emphasis trigger. - style?: ZRStyleProps | false; + style?: (ZRStyleProps & TransitionAnyOption) | false; } interface CustomGroupOption extends CustomBaseElementOption { type: 'group'; @@ -133,7 +159,8 @@ interface CustomGroupOption extends CustomBaseElementOption { children: CustomElementOption[]; $mergeChildren: false | 'byName' | 'byIndex'; } -interface CustomZRPathOption extends CustomDisplayableOption, Pick { +interface CustomZRPathOption extends CustomDisplayableOption { + shape?: PathProps['shape'] & TransitionAnyOption; } interface CustomDuringElProps extends Partial> { shape?: PathProps['shape']; @@ -151,22 +178,21 @@ interface CustomSVGPathOption extends CustomDisplayableOption { y?: number; width?: number; height?: number; - }; + } & TransitionAnyOption; } interface CustomImageOption extends CustomDisplayableOption { type: 'image'; - style?: ImageStyleProps; + style?: ImageStyleProps & TransitionAnyOption; emphasis?: CustomImageOptionOnState; } interface CustomImageOptionOnState extends CustomDisplayableOptionOnState { - style?: ImageStyleProps; + style?: ImageStyleProps & TransitionAnyOption; } interface CustomTextOption extends CustomDisplayableOption { type: 'text'; } type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImageOption | CustomTextOption; type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState; -type StyleOption = ZRStyleProps | ImageStyleProps | false; interface CustomSeriesRenderItemAPI extends @@ -237,6 +263,11 @@ interface CustomSeriesOption extends }; } +interface LooseElementProps extends ElementProps { + style?: ZRStyleProps; + shape?: Dictionary; +} + // Also compat with ec4, where // `visual('color') visual('borderColor')` is supported. const STYLE_VISUAL_TYPE = { @@ -291,6 +322,13 @@ const Z2_SPECIFIED_BIT = { const tmpDuringStyle = {} as CustomDuringElProps['style']; const tmpDuringElProps = {} as CustomDuringElProps; +const LEGACY_TRANSFORM_PROPS = { + position: ['x', 'y'], + scale: ['scaleX', 'scaleY'], + origin: ['originX', 'originY'] +} as const; +type LegacyTransformProp = keyof typeof LEGACY_TRANSFORM_PROPS; + export type PrepareCustomInfo = (coordSys: CoordinateSystem) => { coordSys: CustomSeriesRenderItemParamsCoordSys; api: CustomSeriesRenderItemCoordinateSystemAPI @@ -400,19 +438,18 @@ class CustomSeriesView extends ChartView { // roam or data zoom according to `actionType`. data.diff(oldData) .add(function (newIdx) { - createOrUpdate( + createOrUpdateItemEl( null, newIdx, renderItem(newIdx, payload), customSeries, group, data ); }) .update(function (newIdx, oldIdx) { - const el = oldData.getItemGraphicEl(oldIdx); - createOrUpdate( - el, newIdx, renderItem(newIdx, payload), customSeries, group, data + createOrUpdateItemEl( + oldData.getItemGraphicEl(oldIdx), + newIdx, renderItem(newIdx, payload), customSeries, group, data ); }) .remove(function (oldIdx) { - const el = oldData.getItemGraphicEl(oldIdx); - el && group.remove(el); + removeItemEl(oldData.getItemGraphicEl(oldIdx), customSeries, group); }) .execute(); @@ -455,7 +492,7 @@ class CustomSeriesView extends ChartView { } } for (let idx = params.start; idx < params.end; idx++) { - const el = createOrUpdate(null, idx, renderItem(idx, payload), customSeries, this.group, data); + const el = createOrUpdateItemEl(null, idx, renderItem(idx, payload), customSeries, this.group, data); el.traverse(setIncrementalAndHoverLayer); } } @@ -523,7 +560,7 @@ function createEl(elOption: CustomElementOption): Element { const Clz = graphicUtil.getShapeClass(graphicType); if (__DEV__) { - zrUtil.assert(Clz, 'graphic type "' + graphicType + '" can not be found.'); + assert(Clz, 'graphic type "' + graphicType + '" can not be found.'); } el = new Clz(); @@ -541,7 +578,8 @@ function createEl(elOption: CustomElementOption): Element { } /** - * [STRATEGY] Merge properties or erase all properties: + * ---------------------------------------------------------- + * [STRATEGY_MERGE] Merge properties or erase all properties: * * Based on the fact that the existing zr element probably be reused, we discuss whether * merge or erase all properties to the exsiting elements. @@ -558,7 +596,8 @@ function createEl(elOption: CustomElementOption): Element { * Some "object-like" config like `textConfig`, `textContent`, `style` which are not needed for * every elment, so we replace them only when user specify them. And the that is a total replace. * - * [STRATEGY] `hasOwnProperty` or `== null`: + * ---------------------------------------------- + * [STRATEGY_NULL] `hasOwnProperty` or `== null`: * * Ditinguishing "own property" probably bring little trouble to user when make el options. * So we trade a {xx: null} or {xx: undefined} as "not specified" if possible rather than @@ -566,33 +605,36 @@ function createEl(elOption: CustomElementOption): Element { * clearable props like `style`/`textConfig`/`textContent` we enable `false` to means * "clear". In some othere special cases that the prop is able to set as null/undefined, * but not suitable to use `false`, `hasOwnProperty` is checked. + * + * --------------------------------------------- + * [STRATEGY_TRANSITION] The rule of transition: + * + For props on the root level of a element: + * If there is no `$transition` specified, tansform props will be transitioned by default, + * which is the same as the previous setting in echarts4 and suitable for the scenario + * of dataZoom change. + * If `$transition` specified, only the specified props will be transitioned. + * + For props in `shape` and `style`: + * Only props specified in `$transition` will be transitioned. + * + Break: + * Since ec5, do not make transition to shape by default, because it might result in + * performance issue (especially `points` of polygon) and do not necessary in most cases. */ function updateElNormal( el: Element, dataIndex: number, elOption: CustomElementOption, - styleOpt: StyleOption, + styleOpt: CustomElementOption['style'], attachedTxInfo: AttachedTxInfo, seriesModel: CustomSeriesModel, isInit: boolean, isTextContent: boolean ): void { - const transitionProps = {} as ElementProps; + const transFromProps = {} as ElementProps; + const allProps = {} as ElementProps; const elDisplayable = el.isGroup ? null : el as Displayable; - (elOption as CustomZRPathOption).shape && ( - (transitionProps as PathProps).shape = zrUtil.clone((elOption as CustomZRPathOption).shape) - ); - setLagecyProp(elOption, transitionProps, 'position', 'x', 'y'); - setLagecyProp(elOption, transitionProps, 'scale', 'scaleX', 'scaleY'); - setLagecyProp(elOption, transitionProps, 'origin', 'originX', 'originY'); - setTransProp(elOption, transitionProps, 'x'); - setTransProp(elOption, transitionProps, 'y'); - setTransProp(elOption, transitionProps, 'scaleX'); - setTransProp(elOption, transitionProps, 'scaleY'); - setTransProp(elOption, transitionProps, 'originX'); - setTransProp(elOption, transitionProps, 'originY'); - setTransProp(elOption, transitionProps, 'rotation'); + prepareShapeUpdate(el, elOption, allProps, transFromProps, isInit); + prepareTransformUpdate(el, elOption, allProps, transFromProps, isInit); const txCfgOpt = attachedTxInfo && attachedTxInfo.normal.cfg; if (txCfgOpt) { @@ -601,55 +643,59 @@ function updateElNormal( el.setTextConfig(txCfgOpt); } - if (el.type === 'image' && styleOpt) { - const targetStyle = (transitionProps as Displayable).style = {}; - const imgStyle = (el as graphicUtil.Image).style; - prepareStyleTransition('x', targetStyle, styleOpt, imgStyle, isInit); - prepareStyleTransition('y', targetStyle, styleOpt, imgStyle, isInit); - prepareStyleTransition('width', targetStyle, styleOpt, imgStyle, isInit); - prepareStyleTransition('height', targetStyle, styleOpt, imgStyle, isInit); - } - if (el.type === 'text' && styleOpt) { const textOptionStyle = styleOpt as TextStyleProps; - const targetStyle = (transitionProps as Displayable).style = {}; - const textStyle = (el as graphicUtil.Text).style; - prepareStyleTransition('x', targetStyle, textOptionStyle, textStyle, isInit); - prepareStyleTransition('y', targetStyle, textOptionStyle, textStyle, isInit); // Compatible with ec4: if `textFill` or `textStroke` exists use them. - zrUtil.hasOwn(textOptionStyle, 'textFill') && ( + hasOwn(textOptionStyle, 'textFill') && ( textOptionStyle.fill = (textOptionStyle as any).textFill ); - zrUtil.hasOwn(textOptionStyle, 'textStroke') && ( + hasOwn(textOptionStyle, 'textStroke') && ( textOptionStyle.stroke = (textOptionStyle as any).textStroke ); } + prepareStyleUpdate(el, styleOpt, transFromProps, isInit); + if (elDisplayable) { // PENDING: here the input style object is used directly. // Good for performance but bad for compatibility control. styleOpt && elDisplayable.useStyle(styleOpt); - // Init animation. - if (isInit) { - elDisplayable.style.opacity = 0; - const targetOpacity = (styleOpt && styleOpt.opacity != null) ? styleOpt.opacity : 1; - graphicUtil.initProps(elDisplayable, {style: {opacity: targetOpacity}}, seriesModel, dataIndex); + // When style object changed, how to trade the existing animation? + // It is probably conplicated and not needed to cover all the cases. + // But still need consider the case: + // (1) When using init animation on `style.opacity`, and before the animation + // ended users triggers an update by mousewhell. At that time the init + // animation should better be continued rather than terminated. + // So after `useStyle` called, we should change the animation target manually + // to continue the effect of the init animation. + // (2) PENDING: If the previous animation targeted at a `val1`, and currently we need + // to update the value to `val2` and no animation declared, should be terminate + // the previous animation or just modify the target of the animation? + // Therotically That will happen not only on `style` but also on `shape` and + // `transfrom` props. But we haven't handle this case at present yet. + // (3) PENDING: Is it proper to visit `animators` and `targetName`? + const animators = elDisplayable.animators; + for (let i = 0; i < animators.length; i++) { + const animator = animators[i]; + // targetName is the "topKey". + if (animator.targetName === 'style') { + animator.changeTarget(elDisplayable.style); + } } - zrUtil.hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible); + hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible); } - if (isInit) { - el.attr(transitionProps); - } - else { - graphicUtil.updateProps(el, transitionProps, seriesModel, dataIndex); - } + el.attr(allProps); + const params = {dataIndex: dataIndex, isFrom: true}; + isInit + ? graphicUtil.initProps(el, transFromProps, seriesModel, params) + : graphicUtil.updateProps(el, transFromProps, seriesModel, params); // Merge by default. - zrUtil.hasOwn(elOption, 'silent') && (el.silent = elOption.silent); - zrUtil.hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore); + hasOwn(elOption, 'silent') && (el.silent = elOption.silent); + hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore); const customDuringMounted = el.updateDuringAnimation === elUpdateDuringAnimation; if (elOption.during) { @@ -668,12 +714,223 @@ function updateElNormal( // `elOption.info` enables user to mount some info on // elements and use them in event handlers. // Update them only when user specified, otherwise, remain. - zrUtil.hasOwn(elOption, 'info') && (inner(el).info = elOption.info); + hasOwn(elOption, 'info') && (inner(el).info = elOption.info); } styleOpt ? el.dirty() : el.markRedraw(); } +// See [STRATEGY_TRANSITION] +function prepareShapeUpdate( + el: Element, + elOption: CustomElementOption, + allProps: LooseElementProps, + transFromProps: LooseElementProps, + isInit: boolean +): void { + + const shapeOpt = (elOption as CustomElementOption).shape; + if (!shapeOpt) { + return; + } + + const elShape = (el as LooseElementProps).shape; + let tranFromShapeProps: LooseElementProps['shape']; + + const enterFrom = shapeOpt.$enterFrom; + if (isInit && enterFrom) { + !tranFromShapeProps && (tranFromShapeProps = transFromProps.shape = {}); + const enterFromKeys = keys(enterFrom); + for (let i = 0; i < enterFromKeys.length; i++) { + // `$enterFrom` props are not necessarily also declared in `shape`/`style`/..., + // for example, `opacity` can only declared in `$enterFrom` but not in `style`. + const key = enterFromKeys[i]; + // Do not clone, animator will perform that clone. + tranFromShapeProps[key] = enterFrom[key]; + } + } + + if (!isInit && elShape && shapeOpt.$transition) { + !tranFromShapeProps && (tranFromShapeProps = transFromProps.shape = {}); + const transitionKeys = normalizeToArray(shapeOpt.$transition); + for (let i = 0; i < transitionKeys.length; i++) { + const key = transitionKeys[i]; + const elVal = elShape[key]; + if (__DEV__) { + checkTansitionRefer(key, (shapeOpt as any)[key], elVal); + } + // Do not clone, see `checkTansitionRefer`. + tranFromShapeProps[key] = elVal; + } + } + + const allPropsShape = allProps.shape = {} as LooseElementProps['shape']; + const shapeOptKeys = keys(shapeOpt); + for (let i = 0; i < shapeOptKeys.length; i++) { + const key = shapeOptKeys[i]; + // To avoid share one object with different element, and + // to avoid user modify the object inexpectedly, have to clone. + allPropsShape[key] = cloneValue((shapeOpt as any)[key]); + } + + const leaveTo = shapeOpt.$leaveTo; + if (leaveTo) { + const leaveToProps = getOrCreateLeaveToPropsFromEl(el); + const leaveToShapeProps = leaveToProps.shape || (leaveToProps.shape = {}); + const leaveToKeys = keys(leaveTo); + for (let i = 0; i < leaveToKeys.length; i++) { + const key = leaveToKeys[i]; + leaveToShapeProps[key] = leaveTo[key]; + } + } +} + +// See [STRATEGY_TRANSITION]. +function prepareTransformUpdate( + el: Element, + elOption: CustomElementOption, + allProps: ElementProps, + transFromProps: ElementProps, + isInit: boolean +): void { + const enterFrom = elOption.$enterFrom; + if (isInit && enterFrom) { + const enterFromKeys = keys(enterFrom); + for (let i = 0; i < enterFromKeys.length; i++) { + const key = enterFromKeys[i] as TransformProps; + if (__DEV__) { + checkTransformPropRefer(key, 'el.$enterFrom'); + } + // Do not clone, animator will perform that clone. + transFromProps[key] = enterFrom[key] as number; + } + } + + if (!isInit) { + if (elOption.$transition) { + const transitionKeys = normalizeToArray(elOption.$transition); + for (let i = 0; i < transitionKeys.length; i++) { + const key = transitionKeys[i]; + const elVal = el[key]; + if (__DEV__) { + checkTransformPropRefer(key, 'el.$transition'); + checkTansitionRefer(key, elOption[key], elVal); + } + // Do not clone, see `checkTansitionRefer`. + transFromProps[key] = elVal; + } + } + // This default transition see [STRATEGY_TRANSITION] + else { + setLagecyProp(elOption, transFromProps, 'position', el); + setTransProp(elOption, transFromProps, 'x', el); + setTransProp(elOption, transFromProps, 'y', el); + } + } + + setLagecyProp(elOption, allProps, 'position'); + setLagecyProp(elOption, allProps, 'scale'); + setLagecyProp(elOption, allProps, 'origin'); + setTransProp(elOption, allProps, 'x'); + setTransProp(elOption, allProps, 'y'); + setTransProp(elOption, allProps, 'scaleX'); + setTransProp(elOption, allProps, 'scaleY'); + setTransProp(elOption, allProps, 'originX'); + setTransProp(elOption, allProps, 'originY'); + setTransProp(elOption, allProps, 'rotation'); + + const leaveTo = elOption.$leaveTo; + if (leaveTo) { + const leaveToProps = getOrCreateLeaveToPropsFromEl(el); + const leaveToKeys = keys(leaveTo); + for (let i = 0; i < leaveToKeys.length; i++) { + const key = leaveToKeys[i] as TransformProps; + if (__DEV__) { + checkTransformPropRefer(key, 'el.$leaveTo'); + } + leaveToProps[key] = leaveTo[key] as number; + } + } +} + +// See [STRATEGY_TRANSITION]. +function prepareStyleUpdate( + el: Element, + styleOpt: CustomElementOption['style'], + transFromProps: LooseElementProps, + isInit: boolean +): void { + if (!styleOpt) { + return; + } + + const elStyle = (el as LooseElementProps).style as LooseElementProps['style']; + let transFromStyleProps: LooseElementProps['style']; + + const enterFrom = styleOpt.$enterFrom; + if (isInit && enterFrom) { + const enterFromKeys = keys(enterFrom); + !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); + for (let i = 0; i < enterFromKeys.length; i++) { + const key = enterFromKeys[i]; + // Do not clone, animator will perform that clone. + (transFromStyleProps as any)[key] = enterFrom[key]; + } + } + + if (!isInit && elStyle && styleOpt.$transition) { + const transitionKeys = normalizeToArray(styleOpt.$transition); + !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); + for (let i = 0; i < transitionKeys.length; i++) { + const key = transitionKeys[i]; + const elVal = (elStyle as any)[key]; + if (__DEV__) { + checkTansitionRefer(key, (styleOpt as any)[key], elVal); + } + // Do not clone, see `checkTansitionRefer`. + (transFromStyleProps as any)[key] = elVal; + } + } + + const leaveTo = styleOpt.$leaveTo; + if (leaveTo) { + const leaveToKeys = keys(leaveTo); + const leaveToProps = getOrCreateLeaveToPropsFromEl(el); + const leaveToStyleProps = leaveToProps.style || (leaveToProps.style = {}); + for (let i = 0; i < leaveToKeys.length; i++) { + const key = leaveToKeys[i]; + (leaveToStyleProps as any)[key] = leaveTo[key]; + } + } +} + +function checkTansitionRefer(propName: string, optVal: unknown, elVal: unknown): void { + const isArrLike = isArrayLike(optVal); + assert( + isArrLike || (optVal != null && isFinite(optVal as number)), + 'Prop `' + propName + '` must refer to a finite number or ArrayLike for transition.' + ); + // Try not to copy array for performance, but if user use the same object in different + // call of `renderItem`, it will casue animation transition fail. + assert( + !isArrLike || optVal !== elVal, + 'Prop `' + propName + '` must use different Array object each time for transition.' + ); +} + +function checkTransformPropRefer(key: string, usedIn: string): void { + assert( + hasOwn(TRANSFORM_PROPS, key), + 'Prop `' + key + '` is not a permitted in `' + usedIn + '`. ' + + 'Only `' + keys(TRANSFORM_PROPS).join('`, `') + '` are permitted.' + ); +} + +function getOrCreateLeaveToPropsFromEl(el: Element): LooseElementProps { + const innerEl = inner(el); + return innerEl.leaveToProps || (innerEl.leaveToProps = {}); +} + function elUpdateDuringAnimation(this: Element, key: string): void { const innerEl = inner(this); // FIXME `this.markRedraw();` directly ? @@ -731,7 +988,7 @@ function updateElOnState( state: DisplayStateNonNormal, el: Element, elStateOpt: CustomElementOptionOnState, - styleOpt: StyleOption, + styleOpt: CustomElementOptionOnState['style'], attachedTxInfo: AttachedTxInfo, isRoot: boolean, isTextContent: boolean @@ -842,46 +1099,32 @@ function updateZForEachState( function setLagecyProp( elOption: CustomElementOption, - transitionProps: Partial>, - legacyName: 'position' | 'scale' | 'origin', - xName: TransformPropsX, - yName: TransformPropsY + targetProps: Partial>, + legacyName: LegacyTransformProp, + fromEl?: Element // If provided, retrieve from the element. ): void { const legacyArr = (elOption as any)[legacyName]; - legacyArr && (transitionProps[xName] = legacyArr[0], transitionProps[yName] = legacyArr[1]); + const xyName = LEGACY_TRANSFORM_PROPS[legacyName]; + if (legacyArr) { + if (fromEl) { + targetProps[xyName[0]] = fromEl[xyName[0]]; + targetProps[xyName[1]] = fromEl[xyName[1]]; + } + else { + targetProps[xyName[0]] = legacyArr[0]; + targetProps[xyName[1]] = legacyArr[1]; + } + } } + function setTransProp( elOption: CustomElementOption, - transitionProps: Partial>, - name: TransformProps + targetProps: Partial>, + name: TransformProps, + fromEl?: Element // If provided, retrieve from the element. ): void { - elOption[name] != null && (transitionProps[name] = elOption[name]); -} - -function prepareStyleTransition( - prop: 'x' | 'y', - targetStyle: CustomTextOption['style'], - elOptionStyle: CustomTextOption['style'], - oldElStyle: graphicUtil.Text['style'], - isInit: boolean -): void; -function prepareStyleTransition( - prop: 'x' | 'y' | 'width' | 'height', - targetStyle: CustomImageOption['style'], - elOptionStyle: CustomImageOption['style'], - oldElStyle: graphicUtil.Image['style'], - isInit: boolean -): void; -function prepareStyleTransition( - prop: string, - targetStyle: any, - elOptionStyle: any, - oldElStyle: any, - isInit: boolean -): void { - if (elOptionStyle[prop] != null && !isInit) { - targetStyle[prop] = elOptionStyle[prop]; - elOptionStyle[prop] = oldElStyle[prop]; + if (elOption[name] != null) { + targetProps[name] = fromEl ? fromEl[name] : elOption[name]; } } @@ -897,8 +1140,8 @@ function makeRenderItem( if (coordSys) { if (__DEV__) { - zrUtil.assert(renderItem, 'series.render is required.'); - zrUtil.assert( + assert(renderItem, 'series.render is required.'); + assert( coordSys.prepareCustoms || prepareCustoms[coordSys.type], 'This coordSys does not support custom series.' ); @@ -910,7 +1153,7 @@ function makeRenderItem( : prepareCustoms[coordSys.type](coordSys); } - const userAPI = zrUtil.defaults({ + const userAPI = defaults({ getWidth: api.getWidth, getHeight: api.getHeight, getZr: api.getZr, @@ -987,7 +1230,7 @@ function makeRenderItem( currLabelModels = {}; return renderItem && renderItem( - zrUtil.defaults({ + defaults({ dataIndexInside: dataIndexInside, dataIndex: data.getRawIndex(dataIndexInside), // Can be used for optimization when zoom or roam. @@ -1041,14 +1284,14 @@ function makeRenderItem( visualColor != null && (itemStyle.fill = visualColor); opacity != null && (itemStyle.opacity = opacity); - const opt = {autoColor: zrUtil.isString(visualColor) ? visualColor : '#000'}; + const opt = {autoColor: isString(visualColor) ? visualColor : '#000'}; const labelModel = getLabelModel(dataIndexInside, NORMAL); // Now that the feture of "auto adjust text fill/stroke" has been migrated to zrender // since ec5, we should set `isAttached` as `false` here and make compat in // `convertToEC4StyleForCustomSerise`. const textStyle = graphicUtil.createTextStyle(labelModel, null, opt, false, true); textStyle.text = labelModel.getShallow('show') - ? zrUtil.retrieve2( + ? retrieve2( customSeries.getFormattedLabel(dataIndexInside, NORMAL), getDefaultLabel(data, dataIndexInside) ) @@ -1080,7 +1323,7 @@ function makeRenderItem( const labelModel = getLabelModel(dataIndexInside, EMPHASIS); const textStyle = graphicUtil.createTextStyle(labelModel, null, null, true, true); textStyle.text = labelModel.getShallow('show') - ? zrUtil.retrieve3( + ? retrieve3( customSeries.getFormattedLabel(dataIndexInside, EMPHASIS), customSeries.getFormattedLabel(dataIndexInside, NORMAL), getDefaultLabel(data, dataIndexInside) @@ -1117,7 +1360,7 @@ function makeRenderItem( ): ReturnType { dataIndexInside == null && (dataIndexInside = currDataIndexInside); - if (zrUtil.hasOwn(STYLE_VISUAL_TYPE, visualType)) { + if (hasOwn(STYLE_VISUAL_TYPE, visualType)) { const style = data.getItemVisual(dataIndexInside, 'style'); return style ? style[STYLE_VISUAL_TYPE[visualType as keyof typeof STYLE_VISUAL_TYPE]] as any @@ -1125,7 +1368,7 @@ function makeRenderItem( } // Only support these visuals. Other visual might be inner tricky // for performance (like `style`), do not expose to users. - if (zrUtil.hasOwn(VISUAL_PROPS, visualType)) { + if (hasOwn(VISUAL_PROPS, visualType)) { return data.getItemVisual(dataIndexInside, visualType); } } @@ -1139,7 +1382,7 @@ function makeRenderItem( ): ReturnType { if (coordSys.type === 'cartesian2d') { const baseAxis = coordSys.getBaseAxis() as Axis2D; - return getLayoutOnAxis(zrUtil.defaults({axis: baseAxis}, opt)); + return getLayoutOnAxis(defaults({axis: baseAxis}, opt)); } } @@ -1163,7 +1406,7 @@ function makeRenderItem( function wrapEncodeDef(data: List): Dictionary { const encodeDef = {} as Dictionary; - zrUtil.each(data.dimensions, function (dimName, dataDimIndex) { + each(data.dimensions, function (dimName, dataDimIndex) { const dimInfo = data.getDimensionInfo(dimName); if (!dimInfo.isExtraCoord) { const coordDim = dimInfo.coordDim; @@ -1174,7 +1417,7 @@ function wrapEncodeDef(data: List): Dictionary { return encodeDef; } -function createOrUpdate( +function createOrUpdateItemEl( el: Element, dataIndex: number, elOption: CustomElementOption, @@ -1198,14 +1441,11 @@ function doCreateOrUpdate( ): Element { // [Rule] - // By default, follow merge mode. - // (It probably brings benifit for performance in some cases of large data, where - // user program can be optimized to that only updated props needed to be re-calculated, - // or according to `actionType` some calculation can be skipped.) // If `renderItem` returns `null`/`undefined`/`false`, remove the previous el if existing. // (It seems that violate the "merge" principle, but most of users probably intuitively // regard "return;" as "show nothing element whatever", so make a exception to meet the // most cases.) + // The rule or "merge" see [STRATEGY_MERGE]. // If `elOption` is `null`/`undefined`/`false` (when `renderItem` returns nothing). if (!elOption) { @@ -1287,12 +1527,12 @@ function doesElNeedRecreate(el: Element, elOption: CustomElementOption): boolean && getPathData(elOptionShape) !== elInner.customPathData ) || (elOptionType === 'image' - && zrUtil.hasOwn(elOptionStyle, 'image') + && hasOwn(elOptionStyle, 'image') && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath ) // // FIXME test and remove this restriction? // || (elOptionType === 'text' - // && zrUtil.hasOwn(elOptionStyle, 'text') + // && hasOwn(elOptionStyle, 'text') // && (elOptionStyle as TextStyleProps).text !== elInner.customText // ) ); @@ -1322,7 +1562,7 @@ function doCreateOrUpdateClipPath( if (!clipPath) { clipPath = createEl(clipPathOpt) as graphicUtil.Path; if (__DEV__) { - zrUtil.assert( + assert( clipPath instanceof graphicUtil.Path, 'Only any type of `path` can be used in `clipPath`, rather than ' + clipPath.type + '.' ); @@ -1436,7 +1676,7 @@ function processTxInfo( !txConOptNormal.type && (txConOptNormal.type = 'text'); if (__DEV__) { // Do not tolerate incorret type for forward compat. - txConOptNormal.type !== 'text' && zrUtil.assert( + txConOptNormal.type !== 'text' && assert( txConOptNormal.type === 'text', 'textContent.type must be "text"' ); @@ -1458,7 +1698,7 @@ function retrieveStyleOptionOnState( stateOptionNormal: CustomElementOption, stateOption: CustomElementOptionOnState, state: DisplayStateNonNormal -): StyleOption { +): CustomElementOptionOnState['style'] { let style = stateOption && stateOption.style; if (style == null && state === EMPHASIS && stateOptionNormal) { style = stateOptionNormal.styleEmphasis; @@ -1529,7 +1769,7 @@ function mergeChildren( ); } if (__DEV__) { - zrUtil.assert( + assert( !notMerge || el.childCount() === index, 'MUST NOT contain empty item in children array when `group.$mergeChildren` is `false`.' ); @@ -1583,7 +1823,7 @@ function processAddUpdate( function applyExtraAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void { for (const key in extra) { - if (zrUtil.hasOwn(extra, key)) { + if (hasOwn(extra, key)) { (itemStyle as any)[key] = (extra as any)[key]; } } @@ -1595,6 +1835,23 @@ function processRemove(this: DataDiffer, oldIndex: number): vo child && context.group.remove(child); } +function removeItemEl( + el: Element, + seriesModel: CustomSeriesModel, + group: ViewRootGroup +): void { + if (el) { + const leaveToProps = inner(el).leaveToProps; + leaveToProps + ? graphicUtil.updateProps(el, leaveToProps, seriesModel, { + cb: function () { + group.remove(el); + } + }) + : group.remove(el); + } +} + /** * @return SVG Path data. */ @@ -1604,6 +1861,6 @@ function getPathData(shape: CustomSVGPathOption['shape']): string { } function hasOwnPathData(shape: CustomSVGPathOption['shape']): boolean { - return shape && (zrUtil.hasOwn(shape, 'pathData') || zrUtil.hasOwn(shape, 'd')); + return shape && (hasOwn(shape, 'pathData') || hasOwn(shape, 'd')); } diff --git a/src/component/visualMap/PiecewiseModel.ts b/src/component/visualMap/PiecewiseModel.ts index 085157fa00..ab806aa82b 100644 --- a/src/component/visualMap/PiecewiseModel.ts +++ b/src/component/visualMap/PiecewiseModel.ts @@ -185,7 +185,7 @@ class PiecewiseModel extends VisualMapModel { const isCategory = this.isCategory(); zrUtil.each(option.pieces, function (piece) { - zrUtil.each(visualTypes, function (visualType) { + zrUtil.each(visualTypes, function (visualType: BuiltinVisualProperty) { if (piece.hasOwnProperty(visualType)) { visualTypesInPieces[visualType] = 1; } diff --git a/src/util/graphic.ts b/src/util/graphic.ts index 13db4b54f1..21d6aa2cec 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -71,7 +71,8 @@ import { trim, isArrayLike, map, - defaults + defaults, + isObject } from 'zrender/src/core/util'; @@ -1041,6 +1042,12 @@ export function getFont( ].join(' ')); } +type AnimateOrSetPropsOption = { + dataIndex?: number; + cb?: () => void; + isFrom?: boolean; +}; + function animateOrSetProps( isUpdate: boolean, el: Element, @@ -1048,13 +1055,19 @@ function animateOrSetProps( animatableModel?: Model & { getAnimationDelayParams?: (el: Element, dataIndex: number) => AnimationDelayCallbackParam }, - dataIndex?: number | (() => void), - cb?: () => void + dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, + cb?: AnimateOrSetPropsOption['cb'] ) { + let isFrom = false; if (typeof dataIndex === 'function') { cb = dataIndex; dataIndex = null; } + else if (isObject(dataIndex)) { + cb = dataIndex.cb; + isFrom = dataIndex.isFrom; + dataIndex = dataIndex.dataIndex; + } // Do not check 'animation' property directly here. Consider this case: // animation model is an `itemModel`, whose does not have `isAnimationEnabled` // but its parent model (`seriesModel`) does. @@ -1083,13 +1096,23 @@ function animateOrSetProps( } duration > 0 - ? el.animateTo(props, { - duration, - delay: animationDelay || 0, - easing: animationEasing, - done: cb, - force: !!cb - }) + ? ( + isFrom + ? el.animateFrom(props, { + duration, + delay: animationDelay || 0, + easing: animationEasing, + done: cb, + force: !!cb + }) + : el.animateTo(props, { + duration, + delay: animationDelay || 0, + easing: animationEasing, + done: cb, + force: !!cb + }) + ) : (el.stopAnimation(), el.attr(props), cb && cb()); } else { @@ -1120,8 +1143,8 @@ function updateProps( props: Props, // TODO: TYPE AnimatableModel animatableModel?: Model, - dataIndex?: number | (() => void), - cb?: () => void + dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, + cb?: AnimateOrSetPropsOption['cb'] ) { animateOrSetProps(true, el, props, animatableModel, dataIndex, cb); } @@ -1140,8 +1163,8 @@ export function initProps( el: Element, props: Props, animatableModel?: Model, - dataIndex?: number | (() => void), - cb?: () => void + dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, + cb?: AnimateOrSetPropsOption['cb'] ) { animateOrSetProps(false, el, props, animatableModel, dataIndex, cb); } diff --git a/test/custom-transition-texture.js b/test/custom-transition-texture.js new file mode 100644 index 0000000000..61f9d8fbf7 --- /dev/null +++ b/test/custom-transition-texture.js @@ -0,0 +1 @@ +window.BAR_ROUND_GRADIENT_TEXTURE = 'data:image/png;base64,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'; \ No newline at end of file diff --git a/test/custom-transition.html b/test/custom-transition.html index 2d8c718e42..e1a22ff30e 100644 --- a/test/custom-transition.html +++ b/test/custom-transition.html @@ -36,108 +36,16 @@ -
+
- - - - - - - - +
+
+
+
+
+
@@ -213,7 +121,8 @@ type: 'polygon', shape: { points: makeShapePoints(api, valOnRadius, valOnAngle), - valOnAngle: valOnAngle + valOnAngle: valOnAngle, + $transition: 'valOnAngle' }, style: { lineWidth: 1, @@ -252,7 +161,8 @@ x: point[0], y: point[1], shape: { - valOnAngle: valOnAngle + valOnAngle: valOnAngle, + $transition: 'valOnAngle' }, style: { text: getText(valOnAngle), @@ -440,7 +350,8 @@ points: makeShapePoints(params, widthRadius, startRadius, endRadian), widthRadius: widthRadius, startRadius: startRadius, - endRadian: endRadian + endRadian: endRadian, + $transition: ['widthRadius', 'startRadius', 'endRadian'] }, style: { lineWidth: 1, @@ -492,7 +403,8 @@ shape: { startRadius: startRadius, endRadian: endRadian, - widthRadius: widthRadius + widthRadius: widthRadius, + $transition: ['startRadius', 'endRadian', 'widthRadius'] }, style: { text: makeText(endRadian), @@ -626,10 +538,6 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 2f5de4916dd0652f6fe1b3d5f75853e14c33dd19 Mon Sep 17 00:00:00 2001 From: 100pah Date: Fri, 5 Jun 2020 10:21:58 +0800 Subject: [PATCH 09/14] test: add test case for clip init. --- src/chart/custom.ts | 4 +- test/custom-feature.html | 126 ++++++++++++++++-------- test/custom-transition.html | 184 ++++++++++++++++++++++++++---------- 3 files changed, 221 insertions(+), 93 deletions(-) diff --git a/src/chart/custom.ts b/src/chart/custom.ts index cfd1a1f1e5..b35f55b862 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -221,13 +221,13 @@ interface CustomSeriesRenderItemCoordinateSystemAPI { ): number | number[]; } interface CustomSeriesRenderItemParams { - context: {}; + context: Dictionary; seriesId: string; seriesName: string; seriesIndex: number; coordSys: CustomSeriesRenderItemParamsCoordSys; dataInsideLength: number; - encode: ReturnType + encode: ReturnType; } type CustomSeriesRenderItem = ( params: CustomSeriesRenderItemParams, diff --git a/test/custom-feature.html b/test/custom-feature.html index cee7c5370e..be3f4e5b18 100644 --- a/test/custom-feature.html +++ b/test/custom-feature.html @@ -35,9 +35,10 @@ -
-
-
+
+
+
+
+ + + + + - - - @@ -958,6 +963,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + From d0e83c2e79086965578404ee571904a42c5fa8b1 Mon Sep 17 00:00:00 2001 From: 100pah Date: Mon, 22 Jun 2020 20:15:46 +0800 Subject: [PATCH 13/14] fix: little modify in during api: add return this to setter and change name from "setAttr" to "setTransform". --- src/chart/custom.ts | 15 +++++++++------ test/custom-transition.html | 6 ++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/chart/custom.ts b/src/chart/custom.ts index 9485c0e9e4..d7fcccc32e 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -940,19 +940,21 @@ const tmpDuringScope = {} as { }; const customDuringAPI = { // Usually other props do not need to be changed in animation during. - setAttr(key: TransformProps, val: unknown): void { - assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `setAttr`.'); + setTransform(key: TransformProps, val: unknown) { + assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.'); tmpDuringScope.el[key] = val as number; + return this; }, - getAttr(key: TransformProps): unknown { - assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `getAttr`.'); + getTransform(key: TransformProps): unknown { + assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.'); return tmpDuringScope.el[key]; }, - setShape(key: string, val: unknown): void { + setShape(key: string, val: unknown) { // In custom series, el other than Path can also has `shape` for intepolating props. const shape = (tmpDuringScope.el as any).shape || ((tmpDuringScope.el as any).shape = {}); shape[key] = val; tmpDuringScope.isShapeDirty = true; + return this; }, getShape(key: string): unknown { const shape = (tmpDuringScope.el as any).shape; @@ -960,12 +962,13 @@ const customDuringAPI = { return shape[key]; } }, - setStyle(key: string, val: unknown): void { + setStyle(key: string, val: unknown) { const style = (tmpDuringScope.el as Displayable).style; if (style) { style[key] = val; tmpDuringScope.isStyleDirty = true; } + return this; }, getStyle(key: string): unknown { const style = (tmpDuringScope.el as Displayable).style; diff --git a/test/custom-transition.html b/test/custom-transition.html index cd9c865883..f757125a7f 100644 --- a/test/custom-transition.html +++ b/test/custom-transition.html @@ -234,8 +234,7 @@ during: function (apiDuring) { var iValOnAngle = apiDuring.getShape('valOnAngle'); var point = makeLabelPosition(api, valOnRadius, iValOnAngle); - apiDuring.setAttr('x', point[0]); - apiDuring.setAttr('y', point[1]); + apiDuring.setTransform('x', point[0]).setTransform('y', point[1]); apiDuring.setStyle('text', getText(iValOnAngle)); } }); @@ -489,8 +488,7 @@ apiDuring.getShape('startRadius'), endRadian ); - apiDuring.setAttr('x', point[0]); - apiDuring.setAttr('y', point[1]); + apiDuring.setTransform('x', point[0]).setTransform('y', point[1]); apiDuring.setStyle('text', makeText(endRadian)); } }); From a7d4f698168ece5fa24cc318a041e600e429a5d1 Mon Sep 17 00:00:00 2001 From: 100pah Date: Tue, 23 Jun 2020 12:39:19 +0800 Subject: [PATCH 14/14] clean code --- src/util/graphic.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/util/graphic.ts b/src/util/graphic.ts index e04cf3a304..fe27f60e2c 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -79,9 +79,8 @@ import { } from 'zrender/src/core/util'; import * as numberUtil from './number'; import SeriesModel from '../model/Series'; -import {OnframeCallback, interpolateNumber} from 'zrender/src/animation/Animator'; +import {interpolateNumber} from 'zrender/src/animation/Animator'; import List from '../data/List'; -import DataFormatMixin from '../model/mixin/dataFormat'; const mathMax = Math.max;