/* * 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 * as zrUtil from 'zrender/src/core/util'; import * as graphic from '../../util/graphic'; import { toggleHoverEmphasis } from '../../util/states'; import {createSymbol, normalizeSymbolOffset} from '../../util/symbol'; import {parsePercent, isNumeric} from '../../util/number'; import ChartView from '../../view/Chart'; import PictorialBarSeriesModel, {PictorialBarDataItemOption} from './PictorialBarSeries'; import ExtensionAPI from '../../core/ExtensionAPI'; import SeriesData from '../../data/SeriesData'; import GlobalModel from '../../model/Global'; import Model from '../../model/Model'; import { ColorString, AnimationOptionMixin, ECElement } from '../../util/types'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import type Displayable from 'zrender/src/graphic/Displayable'; import type Axis2D from '../../coord/cartesian/Axis2D'; import type Element from 'zrender/src/Element'; import { getDefaultLabel } from '../helper/labelHelper'; import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path'; import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; import ZRImage from 'zrender/src/graphic/Image'; import { getECData } from '../../util/innerStore'; const BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'borderWidth'] as const; // index: +isHorizontal const LAYOUT_ATTRS = [ {xy: 'x', wh: 'width', index: 0, posDesc: ['left', 'right']}, {xy: 'y', wh: 'height', index: 1, posDesc: ['top', 'bottom']} ] as const; const pathForLineWidth = new graphic.Circle(); type ItemModel = Model & { getAnimationDelayParams(path: any): {index: number, count: number} isAnimationEnabled(): boolean }; type RectShape = graphic.Rect['shape']; type RectLayout = RectShape; type PictorialSymbol = ReturnType & { __pictorialAnimationIndex: number __pictorialRepeatTimes: number }; interface SymbolMeta { dataIndex: number symbolPatternSize: number symbolType: string symbolMargin: number symbolSize: number[] symbolScale: number[] symbolRepeat: PictorialBarDataItemOption['symbolRepeat'] symbolClip: PictorialBarDataItemOption['symbolClip'] symbolRepeatDirection: PictorialBarDataItemOption['symbolRepeatDirection'] layout: RectLayout repeatTimes: number rotation: number pathPosition: number[] bundlePosition: number[] pxSign: number barRectShape: RectShape clipShape: RectShape boundingLength: number repeatCutLength: number valueLineWidth: number opacity: number style: PathStyleProps z2: number itemModel: ItemModel animationModel?: ItemModel hoverScale: boolean } interface CreateOpts { ecSize: { width: number, height: number } seriesModel: PictorialBarSeriesModel coordSys: Cartesian2D coordSysExtent: number[][] isHorizontal: boolean valueDim: typeof LAYOUT_ATTRS[number] categoryDim: typeof LAYOUT_ATTRS[number] } interface PictorialBarElement extends graphic.Group { __pictorialBundle: graphic.Group __pictorialShapeStr: string __pictorialSymbolMeta: SymbolMeta __pictorialMainPath: PictorialSymbol __pictorialBarRect: graphic.Rect __pictorialClipPath: graphic.Rect } class PictorialBarView extends ChartView { static type = 'pictorialBar'; readonly type = PictorialBarView.type; private _data: SeriesData; render( seriesModel: PictorialBarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI ) { const group = this.group; const data = seriesModel.getData(); const oldData = this._data; const cartesian = seriesModel.coordinateSystem; const baseAxis = cartesian.getBaseAxis(); const isHorizontal = baseAxis.isHorizontal(); const coordSysRect = cartesian.master.getRect(); const opt: CreateOpts = { ecSize: {width: api.getWidth(), height: api.getHeight()}, seriesModel: seriesModel, coordSys: cartesian, coordSysExtent: [ [coordSysRect.x, coordSysRect.x + coordSysRect.width], [coordSysRect.y, coordSysRect.y + coordSysRect.height] ], isHorizontal: isHorizontal, valueDim: LAYOUT_ATTRS[+isHorizontal], categoryDim: LAYOUT_ATTRS[1 - (+isHorizontal)] }; data.diff(oldData) .add(function (dataIndex) { if (!data.hasValue(dataIndex)) { return; } const itemModel = getItemModel(data, dataIndex); const symbolMeta = getSymbolMeta(data, dataIndex, itemModel, opt); const bar = createBar(data, opt, symbolMeta); data.setItemGraphicEl(dataIndex, bar); group.add(bar); updateCommon(bar, opt, symbolMeta); }) .update(function (newIndex, oldIndex) { let bar = oldData.getItemGraphicEl(oldIndex) as PictorialBarElement; if (!data.hasValue(newIndex)) { group.remove(bar); return; } const itemModel = getItemModel(data, newIndex); const symbolMeta = getSymbolMeta(data, newIndex, itemModel, opt); const pictorialShapeStr = getShapeStr(data, symbolMeta); if (bar && pictorialShapeStr !== bar.__pictorialShapeStr) { group.remove(bar); data.setItemGraphicEl(newIndex, null); bar = null; } if (bar) { updateBar(bar, opt, symbolMeta); } else { bar = createBar(data, opt, symbolMeta, true); } data.setItemGraphicEl(newIndex, bar); bar.__pictorialSymbolMeta = symbolMeta; // Add back group.add(bar); updateCommon(bar, opt, symbolMeta); }) .remove(function (dataIndex) { const bar = oldData.getItemGraphicEl(dataIndex) as PictorialBarElement; bar && removeBar( oldData, dataIndex, bar.__pictorialSymbolMeta.animationModel, bar ); }) .execute(); this._data = data; return this.group; } remove(ecModel: GlobalModel, api: ExtensionAPI) { const group = this.group; const data = this._data; if (ecModel.get('animation')) { if (data) { data.eachItemGraphicEl(function (bar: PictorialBarElement) { removeBar(data, getECData(bar).dataIndex, ecModel as Model, bar); }); } } else { group.removeAll(); } } } // Set or calculate default value about symbol, and calculate layout info. function getSymbolMeta( data: SeriesData, dataIndex: number, itemModel: ItemModel, opt: CreateOpts ): SymbolMeta { const layout = data.getItemLayout(dataIndex) as RectLayout; const symbolRepeat = itemModel.get('symbolRepeat'); const symbolClip = itemModel.get('symbolClip'); const symbolPosition = itemModel.get('symbolPosition') || 'start'; const symbolRotate = itemModel.get('symbolRotate'); const rotation = (symbolRotate || 0) * Math.PI / 180 || 0; const symbolPatternSize = itemModel.get('symbolPatternSize') || 2; const isAnimationEnabled = itemModel.isAnimationEnabled(); const symbolMeta: SymbolMeta = { dataIndex: dataIndex, layout: layout, itemModel: itemModel, symbolType: data.getItemVisual(dataIndex, 'symbol') || 'circle', style: data.getItemVisual(dataIndex, 'style'), symbolClip: symbolClip, symbolRepeat: symbolRepeat, symbolRepeatDirection: itemModel.get('symbolRepeatDirection'), symbolPatternSize: symbolPatternSize, rotation: rotation, animationModel: isAnimationEnabled ? itemModel : null, hoverScale: isAnimationEnabled && itemModel.get(['emphasis', 'scale']), z2: itemModel.getShallow('z', true) || 0 } as SymbolMeta; prepareBarLength(itemModel, symbolRepeat, layout, opt, symbolMeta); prepareSymbolSize( data, dataIndex, layout, symbolRepeat, symbolClip, symbolMeta.boundingLength, symbolMeta.pxSign, symbolPatternSize, opt, symbolMeta ); prepareLineWidth(itemModel, symbolMeta.symbolScale, rotation, opt, symbolMeta); const symbolSize = symbolMeta.symbolSize; const symbolOffset = normalizeSymbolOffset(itemModel.get('symbolOffset'), symbolSize); prepareLayoutInfo( itemModel, symbolSize, layout, symbolRepeat, symbolClip, symbolOffset as number[], symbolPosition, symbolMeta.valueLineWidth, symbolMeta.boundingLength, symbolMeta.repeatCutLength, opt, symbolMeta ); return symbolMeta; } // bar length can be negative. function prepareBarLength( itemModel: ItemModel, symbolRepeat: PictorialBarDataItemOption['symbolRepeat'], layout: RectLayout, opt: CreateOpts, outputSymbolMeta: SymbolMeta ) { const valueDim = opt.valueDim; const symbolBoundingData = itemModel.get('symbolBoundingData'); const valueAxis = opt.coordSys.getOtherAxis(opt.coordSys.getBaseAxis()); const zeroPx = valueAxis.toGlobalCoord(valueAxis.dataToCoord(0)); const pxSignIdx = 1 - +(layout[valueDim.wh] <= 0); let boundingLength; if (zrUtil.isArray(symbolBoundingData)) { const symbolBoundingExtent = [ convertToCoordOnAxis(valueAxis, symbolBoundingData[0]) - zeroPx, convertToCoordOnAxis(valueAxis, symbolBoundingData[1]) - zeroPx ]; symbolBoundingExtent[1] < symbolBoundingExtent[0] && (symbolBoundingExtent.reverse()); boundingLength = symbolBoundingExtent[pxSignIdx]; } else if (symbolBoundingData != null) { boundingLength = convertToCoordOnAxis(valueAxis, symbolBoundingData) - zeroPx; } else if (symbolRepeat) { boundingLength = opt.coordSysExtent[valueDim.index][pxSignIdx] - zeroPx; } else { boundingLength = layout[valueDim.wh]; } outputSymbolMeta.boundingLength = boundingLength; if (symbolRepeat) { outputSymbolMeta.repeatCutLength = layout[valueDim.wh]; } // if 'pxSign' means sign of pixel, it can't be zero, or symbolScale will be zero // and when borderWidth be settled, the actual linewidth will be NaN outputSymbolMeta.pxSign = boundingLength > 0 ? 1 : -1; } function convertToCoordOnAxis(axis: Axis2D, value: number) { return axis.toGlobalCoord(axis.dataToCoord(axis.scale.parse(value))); } // Support ['100%', '100%'] function prepareSymbolSize( data: SeriesData, dataIndex: number, layout: RectLayout, symbolRepeat: PictorialBarDataItemOption['symbolRepeat'], symbolClip: unknown, boundingLength: number, pxSign: number, symbolPatternSize: number, opt: CreateOpts, outputSymbolMeta: SymbolMeta ) { const valueDim = opt.valueDim; const categoryDim = opt.categoryDim; const categorySize = Math.abs(layout[categoryDim.wh]); const symbolSize = data.getItemVisual(dataIndex, 'symbolSize'); let parsedSymbolSize: number[]; if (zrUtil.isArray(symbolSize)) { parsedSymbolSize = symbolSize.slice(); } else { if (symbolSize == null) { // will parse to number below parsedSymbolSize = ['100%', '100%'] as unknown as number[]; } else { parsedSymbolSize = [symbolSize, symbolSize]; } } // Note: percentage symbolSize (like '100%') do not consider lineWidth, because it is // to complicated to calculate real percent value if considering scaled lineWidth. // So the actual size will bigger than layout size if lineWidth is bigger than zero, // which can be tolerated in pictorial chart. parsedSymbolSize[categoryDim.index] = parsePercent( parsedSymbolSize[categoryDim.index], categorySize ); parsedSymbolSize[valueDim.index] = parsePercent( parsedSymbolSize[valueDim.index], symbolRepeat ? categorySize : Math.abs(boundingLength) ); outputSymbolMeta.symbolSize = parsedSymbolSize; // If x or y is less than zero, show reversed shape. const symbolScale = outputSymbolMeta.symbolScale = [ parsedSymbolSize[0] / symbolPatternSize, parsedSymbolSize[1] / symbolPatternSize ]; // Follow convention, 'right' and 'top' is the normal scale. symbolScale[valueDim.index] *= (opt.isHorizontal ? -1 : 1) * pxSign; } function prepareLineWidth( itemModel: ItemModel, symbolScale: number[], rotation: number, opt: CreateOpts, outputSymbolMeta: SymbolMeta ) { // In symbols are drawn with scale, so do not need to care about the case that width // or height are too small. But symbol use strokeNoScale, where acture lineWidth should // be calculated. let valueLineWidth = itemModel.get(BAR_BORDER_WIDTH_QUERY) || 0; if (valueLineWidth) { pathForLineWidth.attr({ scaleX: symbolScale[0], scaleY: symbolScale[1], rotation: rotation }); pathForLineWidth.updateTransform(); valueLineWidth /= pathForLineWidth.getLineScale(); valueLineWidth *= symbolScale[opt.valueDim.index]; } outputSymbolMeta.valueLineWidth = valueLineWidth || 0; } function prepareLayoutInfo( itemModel: ItemModel, symbolSize: number[], layout: RectLayout, symbolRepeat: PictorialBarDataItemOption['symbolRepeat'], symbolClip: PictorialBarDataItemOption['symbolClip'], symbolOffset: number[], symbolPosition: PictorialBarDataItemOption['symbolPosition'], valueLineWidth: number, boundingLength: number, repeatCutLength: number, opt: CreateOpts, outputSymbolMeta: SymbolMeta ) { const categoryDim = opt.categoryDim; const valueDim = opt.valueDim; const pxSign = outputSymbolMeta.pxSign; const unitLength = Math.max(symbolSize[valueDim.index] + valueLineWidth, 0); let pathLen = unitLength; // Note: rotation will not effect the layout of symbols, because user may // want symbols to rotate on its center, which should not be translated // when rotating. if (symbolRepeat) { const absBoundingLength = Math.abs(boundingLength); let symbolMargin = zrUtil.retrieve(itemModel.get('symbolMargin'), '15%') + ''; let hasEndGap = false; if (symbolMargin.lastIndexOf('!') === symbolMargin.length - 1) { hasEndGap = true; symbolMargin = symbolMargin.slice(0, symbolMargin.length - 1); } let symbolMarginNumeric = parsePercent(symbolMargin, symbolSize[valueDim.index]); let uLenWithMargin = Math.max(unitLength + symbolMarginNumeric * 2, 0); // When symbol margin is less than 0, margin at both ends will be subtracted // to ensure that all of the symbols will not be overflow the given area. let endFix = hasEndGap ? 0 : symbolMarginNumeric * 2; // Both final repeatTimes and final symbolMarginNumeric area calculated based on // boundingLength. const repeatSpecified = isNumeric(symbolRepeat); let repeatTimes = repeatSpecified ? symbolRepeat as number : toIntTimes((absBoundingLength + endFix) / uLenWithMargin); // Adjust calculate margin, to ensure each symbol is displayed // entirely in the given layout area. const mDiff = absBoundingLength - repeatTimes * unitLength; symbolMarginNumeric = mDiff / 2 / (hasEndGap ? repeatTimes : Math.max(repeatTimes - 1, 1)); uLenWithMargin = unitLength + symbolMarginNumeric * 2; endFix = hasEndGap ? 0 : symbolMarginNumeric * 2; // Update repeatTimes when not all symbol will be shown. if (!repeatSpecified && symbolRepeat !== 'fixed') { repeatTimes = repeatCutLength ? toIntTimes((Math.abs(repeatCutLength) + endFix) / uLenWithMargin) : 0; } pathLen = repeatTimes * uLenWithMargin - endFix; outputSymbolMeta.repeatTimes = repeatTimes; outputSymbolMeta.symbolMargin = symbolMarginNumeric; } const sizeFix = pxSign * (pathLen / 2); const pathPosition = outputSymbolMeta.pathPosition = [] as number[]; pathPosition[categoryDim.index] = layout[categoryDim.wh] / 2; pathPosition[valueDim.index] = symbolPosition === 'start' ? sizeFix : symbolPosition === 'end' ? boundingLength - sizeFix : boundingLength / 2; // 'center' if (symbolOffset) { pathPosition[0] += symbolOffset[0]; pathPosition[1] += symbolOffset[1]; } const bundlePosition = outputSymbolMeta.bundlePosition = [] as number[]; bundlePosition[categoryDim.index] = layout[categoryDim.xy]; bundlePosition[valueDim.index] = layout[valueDim.xy]; const barRectShape = outputSymbolMeta.barRectShape = zrUtil.extend({}, layout); barRectShape[valueDim.wh] = pxSign * Math.max( Math.abs(layout[valueDim.wh]), Math.abs(pathPosition[valueDim.index] + sizeFix) ); barRectShape[categoryDim.wh] = layout[categoryDim.wh]; const clipShape = outputSymbolMeta.clipShape = {} as RectShape; // Consider that symbol may be overflow layout rect. clipShape[categoryDim.xy] = -layout[categoryDim.xy]; clipShape[categoryDim.wh] = opt.ecSize[categoryDim.wh]; clipShape[valueDim.xy] = 0; clipShape[valueDim.wh] = layout[valueDim.wh]; } function createPath(symbolMeta: SymbolMeta) { const symbolPatternSize = symbolMeta.symbolPatternSize; const path = createSymbol( // Consider texture img, make a big size. symbolMeta.symbolType, -symbolPatternSize / 2, -symbolPatternSize / 2, symbolPatternSize, symbolPatternSize ); (path as Displayable).attr({ culling: true }); path.type !== 'image' && path.setStyle({ strokeNoScale: true }); return path as PictorialSymbol; } function createOrUpdateRepeatSymbols( bar: PictorialBarElement, opt: CreateOpts, symbolMeta: SymbolMeta, isUpdate?: boolean ) { const bundle = bar.__pictorialBundle; const symbolSize = symbolMeta.symbolSize; const valueLineWidth = symbolMeta.valueLineWidth; const pathPosition = symbolMeta.pathPosition; const valueDim = opt.valueDim; const repeatTimes = symbolMeta.repeatTimes || 0; let index = 0; const unit = symbolSize[opt.valueDim.index] + valueLineWidth + symbolMeta.symbolMargin * 2; eachPath(bar, function (path) { path.__pictorialAnimationIndex = index; path.__pictorialRepeatTimes = repeatTimes; if (index < repeatTimes) { updateAttr(path, null, makeTarget(index), symbolMeta, isUpdate); } else { updateAttr(path, null, { scaleX: 0, scaleY: 0 }, symbolMeta, isUpdate, function () { bundle.remove(path); }); } // updateHoverAnimation(path, symbolMeta); index++; }); for (; index < repeatTimes; index++) { const path = createPath(symbolMeta); path.__pictorialAnimationIndex = index; path.__pictorialRepeatTimes = repeatTimes; bundle.add(path); const target = makeTarget(index); updateAttr( path, { x: target.x, y: target.y, scaleX: 0, scaleY: 0 }, { scaleX: target.scaleX, scaleY: target.scaleY, rotation: target.rotation }, symbolMeta, isUpdate ); } function makeTarget(index: number) { const position = pathPosition.slice(); // (start && pxSign > 0) || (end && pxSign < 0): i = repeatTimes - index // Otherwise: i = index; const pxSign = symbolMeta.pxSign; let i = index; if (symbolMeta.symbolRepeatDirection === 'start' ? pxSign > 0 : pxSign < 0) { i = repeatTimes - 1 - index; } position[valueDim.index] = unit * (i - repeatTimes / 2 + 0.5) + pathPosition[valueDim.index]; return { x: position[0], y: position[1], scaleX: symbolMeta.symbolScale[0], scaleY: symbolMeta.symbolScale[1], rotation: symbolMeta.rotation }; } } function createOrUpdateSingleSymbol( bar: PictorialBarElement, opt: CreateOpts, symbolMeta: SymbolMeta, isUpdate?: boolean ) { const bundle = bar.__pictorialBundle; let mainPath = bar.__pictorialMainPath; if (!mainPath) { mainPath = bar.__pictorialMainPath = createPath(symbolMeta); bundle.add(mainPath); updateAttr( mainPath, { x: symbolMeta.pathPosition[0], y: symbolMeta.pathPosition[1], scaleX: 0, scaleY: 0, rotation: symbolMeta.rotation }, { scaleX: symbolMeta.symbolScale[0], scaleY: symbolMeta.symbolScale[1] }, symbolMeta, isUpdate ); } else { updateAttr( mainPath, null, { x: symbolMeta.pathPosition[0], y: symbolMeta.pathPosition[1], scaleX: symbolMeta.symbolScale[0], scaleY: symbolMeta.symbolScale[1], rotation: symbolMeta.rotation }, symbolMeta, isUpdate ); } } // bar rect is used for label. function createOrUpdateBarRect( bar: PictorialBarElement, symbolMeta: SymbolMeta, isUpdate?: boolean ) { const rectShape = zrUtil.extend({}, symbolMeta.barRectShape); let barRect = bar.__pictorialBarRect; if (!barRect) { barRect = bar.__pictorialBarRect = new graphic.Rect({ z2: 2, shape: rectShape, silent: true, style: { stroke: 'transparent', fill: 'transparent', lineWidth: 0 } }); (barRect as ECElement).disableMorphing = true; bar.add(barRect); } else { updateAttr(barRect, null, {shape: rectShape}, symbolMeta, isUpdate); } } function createOrUpdateClip( bar: PictorialBarElement, opt: CreateOpts, symbolMeta: SymbolMeta, isUpdate?: boolean ) { // If not clip, symbol will be remove and rebuilt. if (symbolMeta.symbolClip) { let clipPath = bar.__pictorialClipPath; const clipShape = zrUtil.extend({}, symbolMeta.clipShape); const valueDim = opt.valueDim; const animationModel = symbolMeta.animationModel; const dataIndex = symbolMeta.dataIndex; if (clipPath) { graphic.updateProps( clipPath, {shape: clipShape}, animationModel, dataIndex ); } else { clipShape[valueDim.wh] = 0; clipPath = new graphic.Rect({shape: clipShape}); bar.__pictorialBundle.setClipPath(clipPath); bar.__pictorialClipPath = clipPath; const target = {} as RectShape; target[valueDim.wh] = symbolMeta.clipShape[valueDim.wh]; graphic[isUpdate ? 'updateProps' : 'initProps']( clipPath, {shape: target}, animationModel, dataIndex ); } } } function getItemModel(data: SeriesData, dataIndex: number) { const itemModel = data.getItemModel(dataIndex) as ItemModel; itemModel.getAnimationDelayParams = getAnimationDelayParams; itemModel.isAnimationEnabled = isAnimationEnabled; return itemModel; } function getAnimationDelayParams(this: ItemModel, path: PictorialSymbol) { // The order is the same as the z-order, see `symbolRepeatDiretion`. return { index: path.__pictorialAnimationIndex, count: path.__pictorialRepeatTimes }; } function isAnimationEnabled(this: ItemModel) { // `animation` prop can be set on itemModel in pictorial bar chart. return this.parentModel.isAnimationEnabled() && !!this.getShallow('animation'); } function createBar(data: SeriesData, opt: CreateOpts, symbolMeta: SymbolMeta, isUpdate?: boolean) { // bar is the main element for each data. const bar = new graphic.Group() as PictorialBarElement; // bundle is used for location and clip. const bundle = new graphic.Group(); bar.add(bundle); bar.__pictorialBundle = bundle; bundle.x = symbolMeta.bundlePosition[0]; bundle.y = symbolMeta.bundlePosition[1]; if (symbolMeta.symbolRepeat) { createOrUpdateRepeatSymbols(bar, opt, symbolMeta); } else { createOrUpdateSingleSymbol(bar, opt, symbolMeta); } createOrUpdateBarRect(bar, symbolMeta, isUpdate); createOrUpdateClip(bar, opt, symbolMeta, isUpdate); bar.__pictorialShapeStr = getShapeStr(data, symbolMeta); bar.__pictorialSymbolMeta = symbolMeta; return bar; } function updateBar(bar: PictorialBarElement, opt: CreateOpts, symbolMeta: SymbolMeta) { const animationModel = symbolMeta.animationModel; const dataIndex = symbolMeta.dataIndex; const bundle = bar.__pictorialBundle; graphic.updateProps( bundle, { x: symbolMeta.bundlePosition[0], y: symbolMeta.bundlePosition[1] }, animationModel, dataIndex ); if (symbolMeta.symbolRepeat) { createOrUpdateRepeatSymbols(bar, opt, symbolMeta, true); } else { createOrUpdateSingleSymbol(bar, opt, symbolMeta, true); } createOrUpdateBarRect(bar, symbolMeta, true); createOrUpdateClip(bar, opt, symbolMeta, true); } function removeBar( data: SeriesData, dataIndex: number, animationModel: Model, bar: PictorialBarElement ) { // Not show text when animating const labelRect = bar.__pictorialBarRect; labelRect && (labelRect.removeTextContent()); const paths = []; eachPath(bar, function (path) { paths.push(path); }); bar.__pictorialMainPath && paths.push(bar.__pictorialMainPath); // I do not find proper remove animation for clip yet. bar.__pictorialClipPath && (animationModel = null); zrUtil.each(paths, function (path) { graphic.removeElement( path, { scaleX: 0, scaleY: 0 }, animationModel, dataIndex, function () { bar.parent && bar.parent.remove(bar); } ); }); data.setItemGraphicEl(dataIndex, null); } function getShapeStr(data: SeriesData, symbolMeta: SymbolMeta) { return [ data.getItemVisual(symbolMeta.dataIndex, 'symbol') || 'none', !!symbolMeta.symbolRepeat, !!symbolMeta.symbolClip ].join(':'); } function eachPath( bar: PictorialBarElement, cb: (this: Ctx, el: PictorialSymbol) => void, context?: Ctx ) { // Do not use Group#eachChild, because it do not support remove. zrUtil.each(bar.__pictorialBundle.children(), function (el) { el !== bar.__pictorialBarRect && cb.call(context, el); }); } function updateAttr( el: T, immediateAttrs: PathProps, animationAttrs: PathProps, symbolMeta: SymbolMeta, isUpdate?: boolean, cb?: () => void ) { immediateAttrs && el.attr(immediateAttrs); // when symbolCip used, only clip path has init animation, otherwise it would be weird effect. if (symbolMeta.symbolClip && !isUpdate) { animationAttrs && el.attr(animationAttrs); } else { animationAttrs && graphic[isUpdate ? 'updateProps' : 'initProps']( el, animationAttrs, symbolMeta.animationModel, symbolMeta.dataIndex, cb ); } } function updateCommon( bar: PictorialBarElement, opt: CreateOpts, symbolMeta: SymbolMeta ) { const dataIndex = symbolMeta.dataIndex; const itemModel = symbolMeta.itemModel; // Color must be excluded. // Because symbol provide setColor individually to set fill and stroke const emphasisModel = itemModel.getModel('emphasis'); const emphasisStyle = emphasisModel.getModel('itemStyle').getItemStyle(); const blurStyle = itemModel.getModel(['blur', 'itemStyle']).getItemStyle(); const selectStyle = itemModel.getModel(['select', 'itemStyle']).getItemStyle(); const cursorStyle = itemModel.getShallow('cursor'); const focus = emphasisModel.get('focus'); const blurScope = emphasisModel.get('blurScope'); const hoverScale = emphasisModel.get('scale'); eachPath(bar, function (path) { if (path instanceof ZRImage) { const pathStyle = path.style; path.useStyle(zrUtil.extend({ // TODO other properties like dx, dy ? image: pathStyle.image, x: pathStyle.x, y: pathStyle.y, width: pathStyle.width, height: pathStyle.height }, symbolMeta.style)); } else { path.useStyle(symbolMeta.style); } const emphasisState = path.ensureState('emphasis'); emphasisState.style = emphasisStyle; if (hoverScale) { // NOTE: Must after scale is set after updateAttr emphasisState.scaleX = path.scaleX * 1.1; emphasisState.scaleY = path.scaleY * 1.1; } path.ensureState('blur').style = blurStyle; path.ensureState('select').style = selectStyle; cursorStyle && (path.cursor = cursorStyle); path.z2 = symbolMeta.z2; }); const barPositionOutside = opt.valueDim.posDesc[+(symbolMeta.boundingLength > 0)]; const barRect = bar.__pictorialBarRect; setLabelStyle( barRect, getLabelStatesModels(itemModel), { labelFetcher: opt.seriesModel, labelDataIndex: dataIndex, defaultText: getDefaultLabel(opt.seriesModel.getData(), dataIndex), inheritColor: symbolMeta.style.fill as ColorString, defaultOpacity: symbolMeta.style.opacity, defaultOutsidePosition: barPositionOutside } ); toggleHoverEmphasis(bar, focus, blurScope, emphasisModel.get('disabled')); } function toIntTimes(times: number) { const roundedTimes = Math.round(times); // Escapse accurate error return Math.abs(times - roundedTimes) < 1e-4 ? roundedTimes : Math.ceil(times); } export default PictorialBarView;