diff --git a/src/chart/custom.ts b/src/chart/custom.ts index 3b801ff588..d7fcccc32e 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -17,10 +17,11 @@ * under the License. */ -// @ts-nocheck 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'; @@ -30,24 +31,302 @@ 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, normalizeToArray } 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'; +import { cloneValue } from 'zrender/src/animation/Animator'; + + +const inner = makeInner<{ + info: CustomExtraElementInfo; + customPathData: string; + customGraphicType: string; + customImagePath: CustomImageOption['style']['image']; + // customText: string; + txConZ2Set: number; + orginalDuring: Element['updateDuringAnimation']; + customDuring: CustomZRPathOption['during']; + leaveToProps: ElementProps +}, Element>(); + +type CustomExtraElementInfo = Dictionary; +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; +const transformPropNamesStr = keys(TRANSFORM_PROPS).join(', '); + +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; + // For animation diff. + name?: string; + info?: CustomExtraElementInfo; + // `false` means remove the textContent. + textContent?: CustomTextOption | false; + // `false` means remove the clipPath + clipPath?: CustomZRPathOption | false; + // Shape can be set in any el option for custom prop for annimation duration. + shape?: TransitionAnyOption; + // updateDuringAnimation + during?(params: typeof customDuringAPI): void; +}; +interface CustomDisplayableOption extends CustomBaseElementOption, Partial> { + style?: ZRStyleProps & TransitionAnyOption; + // `false` means remove emphasis trigger. + styleEmphasis?: ZRStyleProps | false; + emphasis?: CustomDisplayableOptionOnState; +} +interface CustomDisplayableOptionOnState extends Partial> { + // `false` means remove emphasis trigger. + style?: (ZRStyleProps & TransitionAnyOption) | false; +} +interface CustomGroupOption extends CustomBaseElementOption { + type: 'group'; + width?: number; + height?: number; + // @deprecated + diffChildrenByName?: boolean; + children: CustomElementOption[]; + $mergeChildren: false | 'byName' | 'byIndex'; +} +interface CustomZRPathOption extends CustomDisplayableOption { + shape?: PathProps['shape'] & TransitionAnyOption; +} +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; + } & TransitionAnyOption; +} +interface CustomImageOption extends CustomDisplayableOption { + type: 'image'; + style?: ImageStyleProps & TransitionAnyOption; + emphasis?: CustomImageOptionOnState; +} +interface CustomImageOptionOnState extends CustomDisplayableOptionOnState { + style?: ImageStyleProps & TransitionAnyOption; +} +interface CustomTextOption extends CustomDisplayableOption { + type: 'text'; +} +type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImageOption | CustomTextOption; +type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState; + + +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: Dictionary; + 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; -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']; + // Only works on polar and cartesian2d coordinate system. + clip?: boolean; + + // FIXME needed? + tooltip?: SeriesTooltipOption; + + itemStyle?: ItemStyleOption; + label?: LabelOption; + emphasis?: { + itemStyle?: ItemStyleOption; + label?: LabelOption; + }; +} + +interface LooseElementProps extends ElementProps { + style?: ZRStyleProps; + shape?: Dictionary; +} + +// 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; + +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 +}; /** * To reduce total package size of each coordinate systems, the modules `prepareCustom` @@ -60,7 +339,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 +347,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 +382,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; @@ -154,19 +432,18 @@ ChartView.extend({ // roam or data zoom according to `actionType`. data.diff(oldData) .add(function (newIdx) { - createOrUpdate( + createOrUpdateItem( 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 + createOrUpdateItem( + oldData.getItemGraphicEl(oldIdx), + newIdx, renderItem(newIdx, payload), customSeries, group, data ); }) .remove(function (oldIdx) { - const el = oldData.getItemGraphicEl(oldIdx); - el && group.remove(el); + doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group); }) .execute(); @@ -182,37 +459,39 @@ 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; } } for (let idx = params.start; idx < params.end; idx++) { - const el = createOrUpdate(null, idx, renderItem(idx, payload), customSeries, this.group, data); + const el = createOrUpdateItem(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 +509,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 +529,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(); @@ -273,118 +554,624 @@ function createEl(elOption) { 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(); } - 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); - - 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); - }); +/** + * ---------------------------------------------------------- + * [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. + * + "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. + * + * TODO: there is no hint of 'isFirst' to users. So the performance enhancement can not be + * performed yet. Consider the case: + * (1) setOption to "mergeChildren" with a smaller children count + * (2) Use dataZoom to make an item disappear. + * (3) User dataZoom to make the item display again. At that time, renderItem need to return the + * full option rather than partial option to recreate the element. + * + * ---------------------------------------------- + * [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 + * "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. + * + * --------------------------------------------- + * [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: CustomElementOption['style'], + attachedTxInfo: AttachedTxInfo, + seriesModel: CustomSeriesModel, + isInit: boolean, + isTextContent: boolean +): void { + const transFromProps = {} as ElementProps; + const allProps = {} as ElementProps; + const elDisplayable = el.isGroup ? null : el as Displayable; + + prepareShapeUpdate(el, elOption, allProps, transFromProps, isInit); + prepareTransformUpdate(el, elOption, allProps, transFromProps, isInit); + + 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 === '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; + // Compatible with ec4: if `textFill` or `textStroke` exists use them. + hasOwn(textOptionStyle, 'textFill') && ( + textOptionStyle.fill = (textOptionStyle as any).textFill ); - !elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && ( - elOptionStyle.textStroke = elOptionStyle.stroke + hasOwn(textOptionStyle, 'textStroke') && ( + textOptionStyle.stroke = (textOptionStyle as any).textStroke ); } - if (el.type !== 'group') { - el.useStyle(elOptionStyle); + 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); + + // 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); + } + } + + hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible); + } + + el.attr(allProps); + const params = {dataIndex: dataIndex, isFrom: true}; + isInit + ? graphicUtil.initProps(el, transFromProps, seriesModel, params) + : graphicUtil.updateProps(el, transFromProps, seriesModel, params); - // Init animation. - if (isInit) { - el.style.opacity = 0; - let targetOpacity = elOptionStyle.opacity; - targetOpacity == null && (targetOpacity = 1); - graphicUtil.initProps(el, {style: {opacity: targetOpacity}}, animatableModel, dataIndex); + // Merge by default. + hasOwn(elOption, 'silent') && (el.silent = elOption.silent); + 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 (isInit) { - el.attr(transitionProps); + 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. + hasOwn(elOption, 'info') && (inner(el).info = elOption.info); } - else { - graphicUtil.updateProps(el, transitionProps, animatableModel, dataIndex); + + 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 = {}); +} + +// Use it to avoid it be exposed to user. +const tmpDuringScope = {} as { + el: Element; + isShapeDirty: boolean; + isStyleDirty: boolean; +}; +const customDuringAPI = { + // Usually other props do not need to be changed in animation during. + setTransform(key: TransformProps, val: unknown) { + assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.'); + tmpDuringScope.el[key] = val as number; + return this; + }, + getTransform(key: TransformProps): unknown { + assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.'); + return tmpDuringScope.el[key]; + }, + 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; + if (shape) { + return shape[key]; + } + }, + 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; + if (style) { + return style[key]; + } + } +}; + +function elUpdateDuringAnimation(this: Element, key: string): void { + const innerEl = inner(this); + // FIXME `this.markRedraw();` directly ? + innerEl.orginalDuring.call(this, key); + const customDuring = innerEl.customDuring; + + tmpDuringScope.el = this; + tmpDuringScope.isShapeDirty = false; + tmpDuringScope.isStyleDirty = false; + + customDuring(customDuringAPI); + + if (tmpDuringScope.isShapeDirty && (this as graphicUtil.Path).dirtyShape) { + (this as graphicUtil.Path).dirtyShape(); + } + if (tmpDuringScope.isStyleDirty && (this as Displayable).dirtyStyle) { + (this as Displayable).dirtyStyle(); + } + // markRedraw() will be called by default in during. + + // 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( + state: DisplayStateNonNormal, + el: Element, + elStateOpt: CustomElementOptionOnState, + styleOpt: CustomElementOptionOnState['style'], + 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); } - // 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); 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) { - if (elOptionStyle[prop] != null && !isInit) { - targetStyle[prop] = elOptionStyle[prop]; - elOptionStyle[prop] = oldElStyle[prop]; +function setLagecyProp( + elOption: CustomElementOption, + targetProps: Partial>, + legacyName: LegacyTransformProp, + fromEl?: Element // If provided, retrieve from the element. +): void { + const legacyArr = (elOption as any)[legacyName]; + 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, + targetProps: Partial>, + name: TransformProps, + fromEl?: Element // If provided, retrieve from the element. +): void { + if (elOption[name] != null) { + targetProps[name] = fromEl ? fromEl[name] : elOption[name]; } } -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__) { - 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.' ); } + // `coordSys.prepareCustoms` is used for external coord sys like bmap. prepareResult = coordSys.prepareCustoms - ? coordSys.prepareCustoms() + ? coordSys.prepareCustoms(coordSys) : prepareCustoms[coordSys.type](coordSys); } - const userAPI = zrUtil.defaults({ + const userAPI = defaults({ getWidth: api.getWidth, getHeight: api.getHeight, getZr: api.getZr, @@ -396,9 +1183,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,20 +1198,57 @@ 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({ + defaults({ dataIndexInside: dataIndexInside, dataIndex: data.getRawIndex(dataIndexInside), // Can be used for optimization when zoom or roam. @@ -434,159 +1258,181 @@ 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') - ? zrUtil.retrieve2( - customSeries.getFormattedLabel(dataIndexInside, 'normal'), + 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') + ? retrieve2( + 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') - ? zrUtil.retrieve3( - customSeries.getFormattedLabel(dataIndexInside, 'emphasis'), - customSeries.getFormattedLabel(dataIndexInside, 'normal'), + 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') + ? retrieve3( + 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 applyExtraAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void { + for (const key in extra) { + if (hasOwn(extra, key)) { + (itemStyle as any)[key] = (extra as any)[key]; + } + } + } + + 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 (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 (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(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 = {}; - zrUtil.each(data.dimensions, function (dimName, dataDimIndex) { +function wrapEncodeDef(data: List): Dictionary { + const encodeDef = {} as Dictionary; + each(data.dimensions, function (dimName, dataDimIndex) { const dimInfo = data.getDimensionInfo(dimName); if (!dimInfo.isExtraCoord) { const coordDim = dimInfo.coordDim; @@ -597,72 +1443,297 @@ function wrapEncodeDef(data) { return encodeDef; } -function createOrUpdate(el, dataIndex, elOption, animatableModel, group, data) { - el = doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, true); +function createOrUpdateItem( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + group: ViewRootGroup, + data: List +): Element { + // [Rule] + // 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) { + el && group.remove(el); + return; + } + el = doCreateOrUpdateEl(el, dataIndex, elOption, seriesModel, group, true); el && data.setItemGraphicEl(dataIndex, el); return el; } -function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, isRoot) { +function doCreateOrUpdateEl( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + group: ViewRootGroup, + isRoot: boolean +): 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.) + if (__DEV__) { + assert(elOption, 'should not have an null/undefined element setting'); + } - const simplyRemove = !elOption; // `null`/`undefined`/`false` - elOption = elOption || {}; + let toBeReplacedIdx = -1; + + 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; + + if (!el) { + 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(); + } + + attachedTxInfoTmp.normal.cfg = attachedTxInfoTmp.normal.conOpt = + attachedTxInfoTmp.emphasis.cfg = attachedTxInfoTmp.emphasis.conOpt = null; + attachedTxInfoTmp.isLegacy = false; + + doCreateOrUpdateAttachedTx( + el, dataIndex, elOption, seriesModel, isInit, attachedTxInfoTmp + ); + + doCreateOrUpdateClipPath( + el, dataIndex, elOption, seriesModel, isInit + ); + + 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 (elOption.type === 'group') { + mergeChildren( + el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel + ); + } + + if (toBeReplacedIdx >= 0) { + group.replaceAt(el, toBeReplacedIdx); + } + else { + group.add(el); + } + + 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.shape; + const elOptionShape = (elOption as CustomZRPathOption).shape; const elOptionStyle = elOption.style; - - if (el && ( - simplyRemove - // || elOption.$merge === false + return ( // If `elOptionType` is `null`, follow the merge principle. - || (elOptionType != null - && elOptionType !== el.__customGraphicType + (elOptionType != null + && elOptionType !== elInner.customGraphicType ) || (elOptionType === 'path' - && hasOwnPathData(elOptionShape) && getPathData(elOptionShape) !== el.__customPathData + && hasOwnPathData(elOptionShape) + && getPathData(elOptionShape) !== elInner.customPathData ) || (elOptionType === 'image' - && hasOwn(elOptionStyle, 'image') && elOptionStyle.image !== el.__customImagePath + && hasOwn(elOptionStyle, 'image') + && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath ) - // FIXME test and remove this restriction? - || (elOptionType === 'text' - && hasOwn(elOptionShape, 'text') && elOptionStyle.text !== el.__customText - ) - )) { - group.remove(el); - el = null; + // // FIXME test and remove this restriction? + // || (elOptionType === 'text' + // && 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__) { + 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. +} - // `elOption.type` is undefined when `renderItem` returns nothing. - if (simplyRemove) { +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; } - const isInit = !el; - !el && (el = createEl(elOption)); - updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot); + // 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); - if (elOptionType === 'group') { - mergeChildren(el, dataIndex, elOption, animatableModel, data); + txConStlOptNormal ? textContent.dirty() : textContent.markRedraw(); + } } +} - // Always add whatever already added to ensure sequence. - group.add(el); +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; + } + } - return el; + 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' && 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 +): CustomElementOptionOnState['style'] { + 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 @@ -677,9 +1748,14 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, // // For implementation simpleness, do not provide a direct way to remove sinlge // 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) { +// User can remove a single child by set its `ignore` as `true`. +function mergeChildren( + el: graphicUtil.Group, + dataIndex: number, + elOption: CustomGroupOption, + seriesModel: CustomSeriesModel +): void { + const newChildren = elOption.children; const newLen = newChildren ? newChildren.length : 0; const mergeChildren = elOption.$mergeChildren; @@ -697,9 +1773,8 @@ function mergeChildren(el, dataIndex, elOption, animatableModel, data) { oldChildren: el.children() || [], newChildren: newChildren || [], dataIndex: dataIndex, - animatableModel: animatableModel, - group: el, - data: data + seriesModel: seriesModel, + group: el }); return; } @@ -710,24 +1785,28 @@ function mergeChildren(el, dataIndex, elOption, animatableModel, data) { // might be better performance. let index = 0; for (; index < newLen; index++) { - newChildren[index] && doCreateOrUpdate( + newChildren[index] && doCreateOrUpdateEl( el.childAt(index), dataIndex, newChildren[index], - animatableModel, + seriesModel, el, - data + false ); } - if (__DEV__) { - zrUtil.assert( - !notMerge || el.childCount() === index, - 'MUST NOT contain empty item in children array when `group.$mergeChildren` is `false`.' - ); + for (let i = el.childCount() - 1; i >= index; i--) { + doRemoveEl(el.childAt(i), seriesModel, el); } } -function diffGroupChildren(context) { +type DiffGroupContext = { + oldChildren: Element[], + newChildren: CustomElementOption[], + dataIndex: number, + seriesModel: CustomSeriesModel, + group: graphicUtil.Group +}; +function diffGroupChildren(context: DiffGroupContext) { (new DataDiffer( context.oldChildren, context.newChildren, @@ -741,64 +1820,62 @@ 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; - doCreateOrUpdate( + doCreateOrUpdateEl( child, context.dataIndex, childOption, - context.animatableModel, + context.seriesModel, context.group, - 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 processRemove(this: DataDiffer, oldIndex: number): void { + const context = this.context; + const child = context.oldChildren[oldIndex]; + doRemoveEl(child, context.seriesModel, context.group); } -function applyExtraAfter(itemStyle, extra) { - for (const key in extra) { - if (extra.hasOwnProperty(key) - || !CACHED_LABEL_STYLE_PROPERTIES.hasOwnProperty(key) - ) { - itemStyle[key] = extra[key]; - } +function doRemoveEl( + 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); } } -function processRemove(oldIndex) { - 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 && (hasOwn(shape, 'pathData') || hasOwn(shape, 'd')); } -function hasOwn(host, prop) { - return host && host.hasOwnProperty(prop); -} 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()) { 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/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/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 diff --git a/src/echarts.ts b/src/echarts.ts index 753aba3a08..eeca2de9f1 100644 --- a/src/echarts.ts +++ b/src/echarts.ts @@ -1802,6 +1802,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 @@ -1816,6 +1819,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 94419c698d..9459f24f35 100644 --- a/src/model/Component.ts +++ b/src/model/Component.ts @@ -127,6 +127,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 11a5751a32..c3c2ad2159 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 d6c30e7f50..fe27f60e2c 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -59,6 +59,7 @@ import { DataModel, ECEventData, ZRStyleProps, + TextCommonOption, SeriesOption, ParsedValue, CallbackDataParams @@ -73,13 +74,13 @@ import { trim, isArrayLike, map, - defaults + defaults, + isObject } 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; @@ -89,13 +90,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'; @@ -134,8 +128,6 @@ type TextCommonParams = { forceRich?: boolean - getTextPosition?: (textStyleModel: Model, isEmphasis?: boolean) => string | string[] | number[] - defaultOutsidePosition?: LabelOption['position'] textStyle?: ZRStyleProps @@ -398,12 +390,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 } @@ -793,6 +787,7 @@ export function createTextConfig( opt: TextCommonParams, isEmphasis: boolean ) { + opt = opt || {}; const textConfig: ElementTextConfig = {}; let labelPosition; let labelRotate = textStyleModel.getShallow('rotate'); @@ -801,16 +796,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; @@ -1054,7 +1044,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 @@ -1065,6 +1058,13 @@ export function getFont(opt: LabelOption, ecModel: GlobalModel) { ].join(' ')); } +type AnimateOrSetPropsOption = { + dataIndex?: number; + cb?: () => void; + during?: (percent: number) => void; + isFrom?: boolean; +}; + function animateOrSetProps( isUpdate: boolean, el: Element, @@ -1072,15 +1072,22 @@ function animateOrSetProps( animatableModel?: Model & { getAnimationDelayParams?: (el: Element, dataIndex: number) => AnimationDelayCallbackParam }, - dataIndex?: number | (() => void), - cb?: () => void, - during?: (percent: number) => void + dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, + cb?: AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption['during'], + during?: AnimateOrSetPropsOption['during'] ) { + let isFrom = false; if (typeof dataIndex === 'function') { during = cb; cb = dataIndex; dataIndex = null; } + else if (isObject(dataIndex)) { + cb = dataIndex.cb; + during = dataIndex.during; + 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. @@ -1109,20 +1116,31 @@ function animateOrSetProps( } duration > 0 - ? el.animateTo(props, { - duration, - delay: animationDelay || 0, - easing: animationEasing, - done: cb, - force: !!cb || !!during, - during: during - }) - : (el.stopAnimation(), el.attr(props), cb && cb()); + ? ( + isFrom + ? el.animateFrom(props, { + duration, + delay: animationDelay || 0, + easing: animationEasing, + done: cb, + force: !!cb || !!during, + during: during + }) + : el.animateTo(props, { + duration, + delay: animationDelay || 0, + easing: animationEasing, + done: cb, + force: !!cb || !!during, + during: during + }) + ) + : (el.stopAnimation(), el.attr(props), cb && (cb as AnimateOrSetPropsOption['cb'])()); } else { el.stopAnimation(); - el.attr(props); - cb && cb(); + !isFrom && el.attr(props); + cb && (cb as AnimateOrSetPropsOption['cb'])(); } } @@ -1147,9 +1165,9 @@ function updateProps( props: Props, // TODO: TYPE AnimatableModel animatableModel?: Model, - dataIndex?: number | (() => void), - cb?: () => void, - during?: () => void + dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, + cb?: AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption['during'], + during?: AnimateOrSetPropsOption['during'] ) { animateOrSetProps(true, el, props, animatableModel, dataIndex, cb, during); } @@ -1168,9 +1186,9 @@ export function initProps( el: Element, props: Props, animatableModel?: Model, - dataIndex?: number | (() => void), - cb?: () => void, - during?: () => void + dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, + cb?: AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption['during'], + during?: AnimateOrSetPropsOption['during'] ) { animateOrSetProps(false, el, props, animatableModel, dataIndex, cb, during); } 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 e0077bbde2..50b7f7ce72 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 { @@ -426,6 +427,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..be3f4e5b18 100644 --- a/test/custom-feature.html +++ b/test/custom-feature.html @@ -35,10 +35,10 @@ -
-
-
- +
+
+
+
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 new file mode 100644 index 0000000000..f757125a7f --- /dev/null +++ b/test/custom-transition.html @@ -0,0 +1,1490 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/custom-transition2.html b/test/custom-transition2.html new file mode 100644 index 0000000000..7550c55a03 --- /dev/null +++ b/test/custom-transition2.html @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + 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',