diff --git a/src/ExtensionAPI.ts b/src/ExtensionAPI.ts index cb72d65372..5211f67638 100644 --- a/src/ExtensionAPI.ts +++ b/src/ExtensionAPI.ts @@ -55,8 +55,6 @@ abstract class ExtensionAPI { // Implemented in echarts.js abstract getCoordinateSystems(): CoordinateSystemMaster[]; - - // Implemented in echarts.js abstract getComponentByElement(el: Element): ComponentModel; } diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts index eecb05dc41..2233b7f1d6 100644 --- a/src/chart/bar/BarView.ts +++ b/src/chart/bar/BarView.ts @@ -626,7 +626,6 @@ const elementCreator: { shape: extend({}, layout), z2: 1 }); - // rect.autoBatch = true; rect.name = 'item'; diff --git a/src/chart/bar/PictorialBarView.ts b/src/chart/bar/PictorialBarView.ts index bf1a9d5cbd..b4c7af50ae 100644 --- a/src/chart/bar/PictorialBarView.ts +++ b/src/chart/bar/PictorialBarView.ts @@ -38,6 +38,7 @@ 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'; const BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'borderWidth'] as const; @@ -888,7 +889,18 @@ function updateCommon( const blurScope = emphasisModel.get('blurScope'); eachPath(bar, function (path) { - path.useStyle(symbolMeta.style); + 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; diff --git a/src/chart/custom.ts b/src/chart/custom.ts index e1775f5651..3925f90cdf 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -500,7 +500,7 @@ class CustomSeriesView extends ChartView { function setIncrementalAndHoverLayer(el: Displayable) { if (!el.isGroup) { el.incremental = true; - el.useHoverLayer = true; + el.ensureState('emphasis').hoverLayer = true; } } for (let idx = params.start; idx < params.end; idx++) { diff --git a/src/chart/gauge/GaugeView.ts b/src/chart/gauge/GaugeView.ts index 309bc49c5e..acd83fa17c 100644 --- a/src/chart/gauge/GaugeView.ts +++ b/src/chart/gauge/GaugeView.ts @@ -264,7 +264,9 @@ class GaugeView extends ChartView { y: unitY * (r - splitLineLen - distance) + cy, verticalAlign: unitY < -0.4 ? 'top' : (unitY > 0.4 ? 'bottom' : 'middle'), align: unitX < -0.4 ? 'left' : (unitX > 0.4 ? 'right' : 'center') - }, {inheritColor: autoColor}), + }, { + inheritColor: autoColor + }), silent: true })); } diff --git a/src/chart/heatmap/HeatmapView.ts b/src/chart/heatmap/HeatmapView.ts index 2f04944891..648168cf17 100644 --- a/src/chart/heatmap/HeatmapView.ts +++ b/src/chart/heatmap/HeatmapView.ts @@ -281,7 +281,7 @@ class HeatmapView extends ChartView { // PENDING if (incremental) { // Rect must use hover layer if it's incremental. - rect.useHoverLayer = true; + rect.states.emphasis.hoverLayer = true; } group.add(rect); diff --git a/src/chart/helper/LineDraw.ts b/src/chart/helper/LineDraw.ts index 1aa100e6c6..1dde5d39d4 100644 --- a/src/chart/helper/LineDraw.ts +++ b/src/chart/helper/LineDraw.ts @@ -154,7 +154,8 @@ class LineDraw { incrementalUpdate(taskParams: StageHandlerProgressParams, lineData: ListForLineDraw) { function updateIncrementalAndHover(el: Displayable) { if (!el.isGroup && !isEffectObject(el)) { - el.incremental = el.useHoverLayer = true; + el.incremental = true; + el.ensureState('emphasis').hoverLayer = true; } } diff --git a/src/chart/helper/Polyline.ts b/src/chart/helper/Polyline.ts index 4b3d011874..29d0715312 100644 --- a/src/chart/helper/Polyline.ts +++ b/src/chart/helper/Polyline.ts @@ -68,6 +68,7 @@ class Polyline extends graphic.Group { hoverLineStyle = itemModel.getModel(['emphasis', 'lineStyle']).getLineStyle(); } line.useStyle(lineData.getItemVisual(idx, 'style')); + line.style.fill = null; line.style.strokeNoScale = true; const lineEmphasisState = line.ensureState('emphasis'); diff --git a/src/chart/helper/Symbol.ts b/src/chart/helper/Symbol.ts index fadae153b6..b3e906669e 100644 --- a/src/chart/helper/Symbol.ts +++ b/src/chart/helper/Symbol.ts @@ -29,6 +29,7 @@ import { PathProps } from 'zrender/src/graphic/Path'; import { SymbolDrawSeriesScope, SymbolDrawItemModelOption } from './SymbolDraw'; import { extend } from 'zrender/src/core/util'; import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; +import ZRImage from 'zrender/src/graphic/Image'; type ECSymbol = ReturnType; @@ -260,18 +261,30 @@ class Symbol extends graphic.Group { const symbolStyle = data.getItemVisual(idx, 'style'); const visualColor = symbolStyle.fill; - if (symbolPath.__isEmptyBrush) { - // fill and stroke will be swapped if it's empty. - // So we cloned a new style to avoid it affecting the original style in visual storage. - // TODO Better implementation. No empty logic! - symbolPath.useStyle(extend({}, symbolStyle)); + + if (symbolPath instanceof ZRImage) { + const pathStyle = symbolPath.style; + symbolPath.useStyle(extend({ + // TODO other properties like x, y ? + image: pathStyle.image, + x: pathStyle.x, y: pathStyle.y, + width: pathStyle.width, height: pathStyle.height + }, symbolStyle)); } else { - symbolPath.useStyle(symbolStyle); - } - symbolPath.setColor(visualColor, opts && opts.symbolInnerColor); - symbolPath.style.strokeNoScale = true; + if (symbolPath.__isEmptyBrush) { + // fill and stroke will be swapped if it's empty. + // So we cloned a new style to avoid it affecting the original style in visual storage. + // TODO Better implementation. No empty logic! + symbolPath.useStyle(extend({}, symbolStyle)); + } + else { + symbolPath.useStyle(symbolStyle); + } + symbolPath.setColor(visualColor, opts && opts.symbolInnerColor); + symbolPath.style.strokeNoScale = true; + } const liftZ = data.getItemVisual(idx, 'liftZ'); const z2Origin = this._z2; if (liftZ != null) { diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts index 756a15dad9..1fd78ae5ec 100644 --- a/src/chart/helper/SymbolDraw.ts +++ b/src/chart/helper/SymbolDraw.ts @@ -256,7 +256,8 @@ class SymbolDraw { function updateIncrementalAndHover(el: Displayable) { if (!el.isGroup) { - el.incremental = el.useHoverLayer = true; + el.incremental = true; + el.ensureState('emphasis').hoverLayer = true; } } for (let idx = taskParams.start; idx < taskParams.end; idx++) { diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts index 6f5b6e2dc5..ad8acc4fca 100644 --- a/src/chart/pie/labelLayout.ts +++ b/src/chart/pie/labelLayout.ts @@ -388,6 +388,11 @@ export default function ( label.setStyle({ align: textAlign }); + const selectState = label.states.select; + if (selectState) { + selectState.x += label.x; + selectState.y += label.y; + } } sector.setTextConfig({ inside: isLabelInside diff --git a/src/chart/radar/RadarView.ts b/src/chart/radar/RadarView.ts index f9f2917228..a90f241414 100644 --- a/src/chart/radar/RadarView.ts +++ b/src/chart/radar/RadarView.ts @@ -29,6 +29,7 @@ import { ColorString } from '../../util/types'; import GlobalModel from '../../model/Global'; import { VectorArray } from 'zrender/src/core/vector'; import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; +import ZRImage from 'zrender/src/graphic/Image'; function normalizeSymbolSize(symbolSize: number | number[]) { if (!zrUtil.isArray(symbolSize)) { @@ -223,8 +224,19 @@ class RadarView extends ChartView { const emphasisModel = itemModel.getModel('emphasis'); const itemHoverStyle = emphasisModel.getModel('itemStyle').getItemStyle(); symbolGroup.eachChild(function (symbolPath: RadarSymbol) { - symbolPath.useStyle(itemStyle); - symbolPath.setColor(color); + if (symbolPath instanceof ZRImage) { + const pathStyle = symbolPath.style; + symbolPath.useStyle(zrUtil.extend({ + // TODO other properties like x, y ? + image: pathStyle.image, + x: pathStyle.x, y: pathStyle.y, + width: pathStyle.width, height: pathStyle.height + }, itemStyle)); + } + else { + symbolPath.useStyle(itemStyle); + symbolPath.setColor(color); + } const pathEmphasisState = symbolPath.ensureState('emphasis'); pathEmphasisState.style = zrUtil.clone(itemHoverStyle); diff --git a/src/chart/treemap/TreemapView.ts b/src/chart/treemap/TreemapView.ts index 4162527b4b..1601e01ffd 100644 --- a/src/chart/treemap/TreemapView.ts +++ b/src/chart/treemap/TreemapView.ts @@ -985,6 +985,8 @@ function renderNode( const textStyle = textEl.style; textStyle.truncateMinChar = 2; textStyle.width = width; + textStyle.height = height; + textStyle.lineOverflow = 'truncate'; addDrillDownIcon(textStyle, upperLabelRect, thisLayout); const textEmphasisState = textEl.getState('emphasis'); diff --git a/src/component/axisPointer/axisTrigger.ts b/src/component/axisPointer/axisTrigger.ts index 4018c9fc92..f9fedc9ac5 100644 --- a/src/component/axisPointer/axisTrigger.ts +++ b/src/component/axisPointer/axisTrigger.ts @@ -22,7 +22,7 @@ import * as modelHelper from './modelHelper'; import findPointFromSeries from './findPointFromSeries'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../ExtensionAPI'; -import { Dictionary, Payload, CommonAxisPointerOption } from '../../util/types'; +import { Dictionary, Payload, CommonAxisPointerOption, HighlightPayload, DownplayPayload } from '../../util/types'; import AxisPointerModel, { AxisPointerOption } from './AxisPointerModel'; import { each, curry, bind, extend, Curry1 } from 'zrender/src/core/util'; import { ZRenderType } from 'zrender/src/zrender'; @@ -479,11 +479,19 @@ function dispatchHighDownActually( }); toDownplay.length && api.dispatchAction({ - type: 'downplay', escapeConnect: true, batch: toDownplay - }); + type: 'downplay', + escapeConnect: true, + // Not blur others when highlight in axisPointer. + notBlur: true, + batch: toDownplay + } as DownplayPayload); toHighlight.length && api.dispatchAction({ - type: 'highlight', escapeConnect: true, batch: toHighlight - }); + type: 'highlight', + escapeConnect: true, + // Not blur others when highlight in axisPointer. + notBlur: true, + batch: toHighlight + } as HighlightPayload); } function findInputAxisInfo( diff --git a/src/component/axisPointer/viewHelper.ts b/src/component/axisPointer/viewHelper.ts index 1b6f639552..d379c1a027 100644 --- a/src/component/axisPointer/viewHelper.ts +++ b/src/component/axisPointer/viewHelper.ts @@ -126,7 +126,6 @@ export function buildLabelElOption( text: text, textFont: font, fill: labelModel.getTextColor(), - align: 'center', padding: paddings, backgroundColor: bgColor as ColorString, borderColor: labelModel.get('borderColor') || 'transparent', diff --git a/src/component/dataZoom/SliderZoomModel.ts b/src/component/dataZoom/SliderZoomModel.ts index 6165d0a4ba..c31dcca864 100644 --- a/src/component/dataZoom/SliderZoomModel.ts +++ b/src/component/dataZoom/SliderZoomModel.ts @@ -53,11 +53,21 @@ export interface SliderDataZoomOption extends DataZoomOption, BoxLayoutOptionMix */ borderColor?: ZRColor + /** + * Border radius of the box. + */ + borderRadius?: number | number[] + dataBackground?: { lineStyle?: LineStyleOption areaStyle?: AreaStyleOption } + selectedDataBackground?: { + lineStyle?: LineStyleOption + areaStyle?: AreaStyleOption + } + /** * Color of selected area. */ @@ -71,7 +81,8 @@ export interface SliderDataZoomOption extends DataZoomOption, BoxLayoutOptionMix handleIcon?: string /** - * Percent of the slider height + * number: height of icon. width will be calculated according to the aspect of icon. + * string: percent of the slider height. width will be calculated according to the aspect of icon. */ handleSize?: string | number @@ -88,6 +99,10 @@ export interface SliderDataZoomOption extends DataZoomOption, BoxLayoutOptionMix zoomLock?: boolean textStyle?: LabelOption + + emphasis?: { + handleStyle?: ItemStyleOption + } } @@ -108,38 +123,59 @@ class SliderZoomModel extends DataZoomModel { left: null, // Default align to grid rect. bottom: null, // Default align to grid rect. + borderColor: '#d2dbee', + borderRadius: 3, + backgroundColor: 'rgba(47,69,84,0)', // Background of slider zoom component. + // dataBackgroundColor: '#ddd', dataBackground: { lineStyle: { - color: '#2f4554', - width: 0.5, - opacity: 0.3 + color: '#d2dbee', + width: 0.5 }, areaStyle: { - color: 'rgba(47,69,84,0.3)', - opacity: 0.3 + color: '#d2dbee', + opacity: 0.2 } }, - borderColor: '#ddd', - fillerColor: 'rgba(167,183,204,0.4)', // Color of selected area. - // handleColor: 'rgba(89,170,216,0.95)', // Color of handle. - // eslint-disable-next-line - handleIcon: 'M8.2,13.6V3.9H6.3v9.7H3.1v14.9h3.3v9.7h1.8v-9.7h3.3V13.6H8.2z M9.7,24.4H4.8v-1.4h4.9V24.4z M9.7,19.1H4.8v-1.4h4.9V19.1z', + selectedDataBackground: { + lineStyle: { + color: '#8fb0f7', + width: 0.5 + }, + areaStyle: { + color: '#8fb0f7', + opacity: 0.2 + } + }, + + + fillerColor: 'rgba(135,175,274,0.2)', // Color of selected area. + handleIcon: 'path://M-9.35,34.56V42m0-40V9.5m-2,0h4a2,2,0,0,1,2,2v21a2,2,0,0,1-2,2h-4a2,2,0,0,1-2-2v-21A2,2,0,0,1-11.35,9.5Z', // Percent of the slider height handleSize: '100%', handleStyle: { - color: '#a7b7cc' + color: '#fff', + borderColor: '#ACB8D1' }, showDetail: true, showDataShadow: 'auto', // Default auto decision. realtime: true, zoomLock: false, // Whether disable zoom. + textStyle: { color: '#333' + }, + + emphasis: { + handleStyle: { + borderColor: '#8FB0F7', + borderWidth: 2 + } } }); } diff --git a/src/component/dataZoom/SliderZoomView.ts b/src/component/dataZoom/SliderZoomView.ts index f3342907fb..d775d67f8f 100644 --- a/src/component/dataZoom/SliderZoomView.ts +++ b/src/component/dataZoom/SliderZoomView.ts @@ -37,6 +37,10 @@ import Axis from '../../coord/Axis'; import SeriesModel from '../../model/Series'; import { AxisBaseModel } from '../../coord/AxisBaseModel'; import { getAxisMainType, collectReferCoordSysModelInfo } from './helper'; +import { enableHoverEmphasis } from '../../util/states'; +import { createSymbol, symbolBuildProxies } from '../../util/symbol'; +import { deprecateLog } from '../../util/log'; +import { __DEV__ } from '../../config'; const Rect = graphic.Rect; @@ -50,11 +54,11 @@ const LABEL_GAP = 5; const SHOW_DATA_SHADOW_SERIES_TYPE = ['line', 'bar', 'candlestick', 'scatter']; -type Icon = ReturnType; interface Displayables { - barGroup: graphic.Group; - handles: [Icon, Icon]; + sliderGroup: graphic.Group; + handles: [graphic.Path, graphic.Path]; handleLabels: [graphic.Text, graphic.Text]; + dataShadowSegs: graphic.Group[]; filler: graphic.Rect; } class SliderZoomView extends DataZoomView { @@ -157,7 +161,7 @@ class SliderZoomView extends DataZoomView { this._resetLocation(); this._resetInterval(); - const barGroup = this._displayables.barGroup = new graphic.Group(); + const barGroup = this._displayables.sliderGroup = new graphic.Group(); this._renderBackground(); @@ -225,11 +229,11 @@ class SliderZoomView extends DataZoomView { const targetAxisModel = this.dataZoomModel.getFirstTargetAxisModel(); const inverse = targetAxisModel && targetAxisModel.get('inverse'); - const barGroup = this._displayables.barGroup; + const sliderGroup = this._displayables.sliderGroup; const otherAxisInverse = (this._dataShadowInfo || {}).otherAxisInverse; // Transform barGroup. - barGroup.attr( + sliderGroup.attr( (orient === HORIZONTAL && !inverse) ? {scaleY: otherAxisInverse ? 1 : -1, scaleX: 1 } : (orient === HORIZONTAL && inverse) @@ -241,7 +245,7 @@ class SliderZoomView extends DataZoomView { ); // Position barGroup - const rect = thisGroup.getBoundingRect([barGroup]); + const rect = thisGroup.getBoundingRect([sliderGroup]); thisGroup.x = location.x - rect.x; thisGroup.y = location.y - rect.y; thisGroup.markRedraw(); @@ -254,7 +258,7 @@ class SliderZoomView extends DataZoomView { private _renderBackground() { const dataZoomModel = this.dataZoomModel; const size = this._size; - const barGroup = this._displayables.barGroup; + const barGroup = this._displayables.sliderGroup; barGroup.add(new Rect({ silent: true, @@ -283,6 +287,8 @@ class SliderZoomView extends DataZoomView { private _renderDataShadow() { const info = this._dataShadowInfo = this._prepareDataShadowInfo(); + this._displayables.dataShadowSegs = []; + if (!info) { return; } @@ -352,22 +358,35 @@ class SliderZoomView extends DataZoomView { }); const dataZoomModel = this.dataZoomModel; + + function createDataShadowGroup(isSelectedArea?: boolean) { + const model = dataZoomModel.getModel(isSelectedArea ? 'selectedDataBackground' : 'dataBackground'); + const group = new graphic.Group(); + const polygon = new graphic.Polygon({ + shape: {points: areaPoints}, + segmentIgnoreThreshold: 1, + style: model.getModel('areaStyle').getAreaStyle(), + silent: true, + z2: -20 + }); + const polyline = new graphic.Polyline({ + shape: {points: linePoints}, + segmentIgnoreThreshold: 1, + style: model.getModel('lineStyle').getLineStyle(), + silent: true, + z2: -19 + }); + group.add(polygon); + group.add(polyline); + return group; + } + // let dataBackgroundModel = dataZoomModel.getModel('dataBackground'); - this._displayables.barGroup.add(new graphic.Polygon({ - shape: {points: areaPoints}, - style: defaults( - {fill: dataZoomModel.get('dataBackgroundColor' as any)}, - dataZoomModel.getModel(['dataBackground', 'areaStyle']).getAreaStyle() - ), - silent: true, - z2: -20 - })); - this._displayables.barGroup.add(new graphic.Polyline({ - shape: {points: linePoints}, - style: dataZoomModel.getModel(['dataBackground', 'lineStyle']).getLineStyle(), - silent: true, - z2: -19 - })); + for (let i = 0; i < 3; i++) { + const group = createDataShadowGroup(i === 1); + this._displayables.sliderGroup.add(group); + this._displayables.dataShadowSegs.push(group); + } } private _prepareDataShadowInfo() { @@ -431,11 +450,13 @@ class SliderZoomView extends DataZoomView { const displaybles = this._displayables; const handles: [graphic.Path, graphic.Path] = displaybles.handles = [null, null]; const handleLabels: [graphic.Text, graphic.Text] = displaybles.handleLabels = [null, null]; - const barGroup = this._displayables.barGroup; + const sliderGroup = this._displayables.sliderGroup; const size = this._size; const dataZoomModel = this.dataZoomModel; - barGroup.add(displaybles.filler = new Rect({ + const borderRadius = dataZoomModel.get('borderRadius') || 0; + + sliderGroup.add(displaybles.filler = new Rect({ draggable: true, cursor: getCursor(this._orient), drift: bind(this._onDragMove, this, 'all'), @@ -452,14 +473,15 @@ class SliderZoomView extends DataZoomView { })); // Frame border. - barGroup.add(new Rect({ + sliderGroup.add(new Rect({ silent: true, subPixelOptimize: true, shape: { x: 0, y: 0, width: size[0], - height: size[1] + height: size[1], + r: borderRadius }, style: { stroke: dataZoomModel.get('dataBackgroundColor' as any) // deprecated option @@ -470,31 +492,47 @@ class SliderZoomView extends DataZoomView { })); each([0, 1] as const, function (handleIndex) { - const path = graphic.createIcon( - dataZoomModel.get('handleIcon'), - { - cursor: getCursor(this._orient), - draggable: true, - drift: bind(this._onDragMove, this, handleIndex), - ondragend: bind(this._onDragEnd, this), - onmouseover: bind(this._showDataInfo, this, true), - onmouseout: bind(this._showDataInfo, this, false) - }, - {x: -1, y: 0, width: 2, height: 2} + let iconStr = dataZoomModel.get('handleIcon'); + if (!symbolBuildProxies[iconStr] && iconStr.indexOf('path://') < 0) { + // Compatitable with the old icon parsers. Which can use a path string without path:// + iconStr = 'path://' + iconStr; + if (__DEV__) { + deprecateLog('handleIcon now needs \'path://\' prefix when using a path string'); + } + } + const path = createSymbol( + iconStr, + -1, 0, 2, 2, null, true ) as graphic.Path; + path.attr({ + cursor: getCursor(this._orient), + draggable: true, + drift: bind(this._onDragMove, this, handleIndex), + ondragend: bind(this._onDragEnd, this), + onmouseover: bind(this._showDataInfo, this, true), + onmouseout: bind(this._showDataInfo, this, false) + }); const bRect = path.getBoundingRect(); - this._handleHeight = parsePercent(dataZoomModel.get('handleSize'), this._size[1]); + const handleSize = dataZoomModel.get('handleSize'); + + this._handleHeight = parsePercent(handleSize, this._size[1]); this._handleWidth = bRect.width / bRect.height * this._handleHeight; path.setStyle(dataZoomModel.getModel('handleStyle').getItemStyle()); + path.style.strokeNoScale = true; + path.rectHover = true; + + path.ensureState('emphasis').style = dataZoomModel.getModel(['emphasis', 'handleStyle']).getItemStyle(); + enableHoverEmphasis(path); + const handleColor = dataZoomModel.get('handleColor' as any); // deprecated option // Compatitable with previous version if (handleColor != null) { path.style.fill = handleColor; } - barGroup.add(handles[handleIndex] = path); + sliderGroup.add(handles[handleIndex] = path); const textStyleModel = dataZoomModel.getModel('textStyle'); @@ -578,6 +616,25 @@ class SliderZoomView extends DataZoomView { height: size[1] }); + // update clip path of shadow. + const dataShadowSegs = displaybles.dataShadowSegs; + const segIntervals = [0, handleInterval[0], handleInterval[1], size[0]]; + + for (let i = 0; i < dataShadowSegs.length; i++) { + const segGroup = dataShadowSegs[i]; + let clipPath = segGroup.getClipPath(); + if (!clipPath) { + clipPath = new graphic.Rect(); + segGroup.setClipPath(clipPath); + } + clipPath.setShape({ + x: segIntervals[i], + y: 0, + width: segIntervals[i + 1] - segIntervals[i], + height: size[1] + }); + } + this._updateDataInfo(nonRealtime); } @@ -687,7 +744,7 @@ class SliderZoomView extends DataZoomView { eventTool.stop(event.event); // Transform dx, dy to bar coordination. - const barTransform = this._displayables.barGroup.getLocalTransform(); + const barTransform = this._displayables.sliderGroup.getLocalTransform(); const vertex = graphic.applyTransform([dx, dy], barTransform, true); const changed = this._updateInterval(handleIndex, vertex[0]); @@ -713,7 +770,7 @@ class SliderZoomView extends DataZoomView { private _onClickPanelClick(e: ZRElementEvent) { const size = this._size; - const localPoint = this._displayables.barGroup.transformCoordToLocal(e.offsetX, e.offsetY); + const localPoint = this._displayables.sliderGroup.transformCoordToLocal(e.offsetX, e.offsetY); if (localPoint[0] < 0 || localPoint[0] > size[0] || localPoint[1] < 0 || localPoint[1] > size[1] diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts index 241b63bf90..e2ed82137a 100644 --- a/src/component/legend/LegendView.ts +++ b/src/component/legend/LegendView.ts @@ -39,7 +39,7 @@ import { ColorString } from '../../util/types'; import Model from '../../model/Model'; -import Displayable from 'zrender/src/graphic/Displayable'; +import Displayable, { DisplayableState } from 'zrender/src/graphic/Displayable'; import { PathStyleProps } from 'zrender/src/graphic/Path'; import { parse, stringify } from 'zrender/src/tool/color'; @@ -198,7 +198,7 @@ class LegendView extends ComponentView { if (seriesModel) { const data = seriesModel.getData(); const style = data.getVisual('style'); - const color = style.fill; + const color = style[data.getVisual('drawType')] || style.fill; const borderColor = style.stroke; // Using rect symbol defaultly @@ -585,6 +585,17 @@ function dispatchSelectAction( dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId); } +function isUseHoverLayer(api: ExtensionAPI) { + const list = api.getZr().storage.getDisplayList(); + let emphasisState: DisplayableState; + let i = 0; + const len = list.length; + while (!(emphasisState = list[i].states.emphasis) && i < len) { + i++; + } + return emphasisState && emphasisState.hoverLayer; +} + function dispatchHighlightAction( seriesName: string, dataName: string, @@ -592,8 +603,7 @@ function dispatchHighlightAction( excludeSeriesId: string[] ) { // If element hover will move to a hoverLayer. - const el = api.getZr().storage.getDisplayList()[0]; - if (!(el && el.useHoverLayer)) { + if (!isUseHoverLayer(api)) { api.dispatchAction({ type: 'highlight', seriesName: seriesName, @@ -610,8 +620,7 @@ function dispatchDownplayAction( excludeSeriesId: string[] ) { // If element hover will move to a hoverLayer. - const el = api.getZr().storage.getDisplayList()[0]; - if (!(el && el.useHoverLayer)) { + if (!isUseHoverLayer(api)) { api.dispatchAction({ type: 'downplay', seriesName: seriesName, diff --git a/src/component/timeline/SliderTimelineModel.ts b/src/component/timeline/SliderTimelineModel.ts index 2791773b75..72a96ae141 100644 --- a/src/component/timeline/SliderTimelineModel.ts +++ b/src/component/timeline/SliderTimelineModel.ts @@ -48,13 +48,13 @@ class SliderTimelineModel extends TimelineModel { trigger: 'item' // data item may also have tootip attr. }, - symbol: 'emptyCircle', - symbolSize: 10, + symbol: 'circle', + symbolSize: 12, lineStyle: { show: true, width: 2, - color: '#304654' + color: '#DAE1F5' }, label: { // 文本标签 position: 'auto', // auto left right top bottom @@ -66,19 +66,24 @@ class SliderTimelineModel extends TimelineModel { rotate: 0, // formatter: null, // 其余属性默认使用全局文本样式,详见TEXTSTYLE - color: '#304654' + color: '#A4B1D7' }, itemStyle: { - color: '#304654', + color: '#A4B1D7', borderWidth: 1 }, checkpointStyle: { symbol: 'circle', - symbolSize: 13, - color: '#c23531', - borderWidth: 5, - borderColor: 'rgba(194,53,49, 0.5)', + symbolSize: 15, + color: '#316bf3', + borderColor: '#fff', + borderWidth: 2, + shadowBlur: 2, + shadowOffsetX: 1, + shadowOffsetY: 1, + shadowColor: 'rgba(0, 0, 0, 0.3)', + // borderColor: 'rgba(194,53,49, 0.5)', animation: true, animationDuration: 300, animationEasing: 'quinticInOut' @@ -89,36 +94,54 @@ class SliderTimelineModel extends TimelineModel { showPlayBtn: true, showPrevBtn: true, showNextBtn: true, - itemSize: 22, + + itemSize: 24, itemGap: 12, + position: 'left', // 'left' 'right' 'top' 'bottom' + playIcon: 'path://M31.6,53C17.5,53,6,41.5,6,27.4S17.5,1.8,31.6,1.8C45.7,1.8,57.2,13.3,57.2,27.4S45.7,53,31.6,53z M31.6,3.3 C18.4,3.3,7.5,14.1,7.5,27.4c0,13.3,10.8,24.1,24.1,24.1C44.9,51.5,55.7,40.7,55.7,27.4C55.7,14.1,44.9,3.3,31.6,3.3z M24.9,21.3 c0-2.2,1.6-3.1,3.5-2l10.5,6.1c1.899,1.1,1.899,2.9,0,4l-10.5,6.1c-1.9,1.1-3.5,0.2-3.5-2V21.3z', // jshint ignore:line stopIcon: 'path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z', // jshint ignore:line - nextIcon: 'path://M18.6,50.8l22.5-22.5c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.5-0.3-0.7L18.7,4.4c-0.1-0.1-0.2-0.3-0.2-0.5 c0-0.4,0.3-0.8,0.8-0.8c0.2,0,0.5,0.1,0.6,0.3l23.5,23.5l0,0c0.2,0.2,0.3,0.4,0.3,0.7c0,0.3-0.1,0.5-0.3,0.7l-0.1,0.1L19.7,52 c-0.1,0.1-0.3,0.2-0.5,0.2c-0.4,0-0.8-0.3-0.8-0.8C18.4,51.2,18.5,51,18.6,50.8z', // jshint ignore:line - prevIcon: 'path://M43,52.8L20.4,30.3c-0.2-0.2-0.3-0.4-0.3-0.7c0-0.3,0.1-0.5,0.3-0.7L42.9,6.4c0.1-0.1,0.2-0.3,0.2-0.5 c0-0.4-0.3-0.8-0.8-0.8c-0.2,0-0.5,0.1-0.6,0.3L18.3,28.8l0,0c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.5,0.3,0.7l0.1,0.1L41.9,54 c0.1,0.1,0.3,0.2,0.5,0.2c0.4,0,0.8-0.3,0.8-0.8C43.2,53.2,43.1,53,43,52.8z', // jshint ignore:line + nextIcon: 'M2,18.5A1.52,1.52,0,0,1,.92,18a1.49,1.49,0,0,1,0-2.12L7.81,9.36,1,3.11A1.5,1.5,0,1,1,3,.89l8,7.34a1.48,1.48,0,0,1,.49,1.09,1.51,1.51,0,0,1-.46,1.1L3,18.08A1.5,1.5,0,0,1,2,18.5Z', // jshint ignore:line + prevIcon: 'M10,.5A1.52,1.52,0,0,1,11.08,1a1.49,1.49,0,0,1,0,2.12L4.19,9.64,11,15.89a1.5,1.5,0,1,1-2,2.22L1,10.77A1.48,1.48,0,0,1,.5,9.68,1.51,1.51,0,0,1,1,8.58L9,.92A1.5,1.5,0,0,1,10,.5Z', // jshint ignore:line + + prevBtnSize: 18, + nextBtnSize: 18, - color: '#304654', - borderColor: '#304654', + color: '#A4B1D7', + borderColor: '#A4B1D7', borderWidth: 1 }, - emphasis: { label: { show: true, // 其余属性默认使用全局文本样式,详见TEXTSTYLE - color: '#c23531' + color: '#6f778d' }, itemStyle: { - color: '#c23531' + color: '#316BF3' }, controlStyle: { - color: '#c23531', - borderColor: '#c23531', + color: '#316BF3', + borderColor: '#316BF3', borderWidth: 2 } }, + + progress: { + lineStyle: { + color: '#316BF3' + }, + itemStyle: { + color: '#316BF3' + }, + label: { + color: '#6f778d' + } + }, + data: [] }); diff --git a/src/component/timeline/SliderTimelineView.ts b/src/component/timeline/SliderTimelineView.ts index e0e608339e..4eb888c8b1 100644 --- a/src/component/timeline/SliderTimelineView.ts +++ b/src/component/timeline/SliderTimelineView.ts @@ -30,19 +30,21 @@ import * as numberUtil from '../../util/number'; import {encodeHTML} from '../../util/format'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../ExtensionAPI'; -import { merge, each, extend, clone, isString, bind } from 'zrender/src/core/util'; +import { merge, each, extend, clone, isString, bind, defaults, retrieve2 } from 'zrender/src/core/util'; import SliderTimelineModel from './SliderTimelineModel'; import ComponentView from '../../view/Component'; import { LayoutOrient, ZRTextAlign, ZRTextVerticalAlign, ZRElementEvent } from '../../util/types'; import TimelineModel, { TimelineDataItemOption, TimelineCheckpointStyle } from './TimelineModel'; import { TimelineChangePayload, TimelinePlayChangePayload } from './timelineAction'; import Model from '../../model/Model'; -import { PathProps } from 'zrender/src/graphic/Path'; +import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path'; import Scale from '../../scale/Scale'; import OrdinalScale from '../../scale/Ordinal'; import TimeScale from '../../scale/Time'; import IntervalScale from '../../scale/Interval'; import { VectorArray } from 'zrender/src/core/vector'; +import { parsePercent } from 'zrender/src/contain/text'; +import { makeInner } from '../../util/model'; const PI = Math.PI; @@ -50,8 +52,13 @@ type TimelineSymbol = ReturnType; type RenderMethodName = '_renderAxisLine' | '_renderAxisTick' | '_renderControl' | '_renderCurrentPointer'; +type ControlName = 'play' | 'stop' | 'next' | 'prev'; type ControlIconName = 'playIcon' | 'stopIcon' | 'nextIcon' | 'prevIcon'; +const labelDataIndexStore = makeInner<{ + dataIndex: number +}, graphic.Text>(); + interface LayoutInfo { viewRect: BoundingRect mainLength: number @@ -89,10 +96,14 @@ class SliderTimelineView extends TimelineView { private _currentPointer: TimelineSymbol; + private _progressLine: graphic.Line; + private _mainGroup: graphic.Group; private _labelGroup: graphic.Group; + private _tickSymbols: graphic.Path[]; + private _tickLabels: graphic.Text[]; init(ecModel: GlobalModel, api: ExtensionAPI) { this.api = api; @@ -133,6 +144,8 @@ class SliderTimelineView extends TimelineView { } this._doPlayStop(); + + this._updateTicksStatus(); } /** @@ -150,7 +163,7 @@ class SliderTimelineView extends TimelineView { this._clearTimer(); } - _layout(timelineModel: SliderTimelineModel, api: ExtensionAPI): LayoutInfo { + private _layout(timelineModel: SliderTimelineModel, api: ExtensionAPI): LayoutInfo { const labelPosOpt = timelineModel.get(['label', 'position']); const orient = timelineModel.get('orient'); const viewRect = getViewRect(timelineModel, api); @@ -250,7 +263,7 @@ class SliderTimelineView extends TimelineView { }; } - _position(layoutInfo: LayoutInfo, timelineModel: SliderTimelineModel) { + private _position(layoutInfo: LayoutInfo, timelineModel: SliderTimelineModel) { // Position is be called finally, because bounding rect is needed for // adapt content to fill viewRect (auto adapt offset). @@ -321,7 +334,7 @@ class SliderTimelineView extends TimelineView { } } - _createAxis(layoutInfo: LayoutInfo, timelineModel: SliderTimelineModel) { + private _createAxis(layoutInfo: LayoutInfo, timelineModel: SliderTimelineModel) { const data = timelineModel.getData(); const axisType = timelineModel.get('axisType'); @@ -344,13 +357,13 @@ class SliderTimelineView extends TimelineView { return axis; } - _createGroup(key: '_mainGroup' | '_labelGroup') { + private _createGroup(key: '_mainGroup' | '_labelGroup') { const newGroup = this[key] = new graphic.Group(); this.group.add(newGroup); return newGroup; } - _renderAxisLine( + private _renderAxisLine( layoutInfo: LayoutInfo, group: graphic.Group, axis: TimelineAxis, @@ -362,7 +375,7 @@ class SliderTimelineView extends TimelineView { return; } - group.add(new graphic.Line({ + const line = new graphic.Line({ shape: { x1: axisExtent[0], y1: 0, x2: axisExtent[1], y2: 0 @@ -373,13 +386,27 @@ class SliderTimelineView extends TimelineView { ), silent: true, z2: 1 - })); + }); + group.add(line); + + const progressLine = this._progressLine = new graphic.Line({ + shape: { + x1: axisExtent[0], + x2: this._currentPointer + ? this._currentPointer.x : axisExtent[0], + y1: 0, y2: 0 + }, + style: defaults( + { lineCap: 'round', lineWidth: line.style.lineWidth } as PathStyleProps, + timelineModel.getModel(['progress', 'lineStyle']).getLineStyle() + ), + silent: true, + z2: 1 + }); + group.add(progressLine); } - /** - * @private - */ - _renderAxisTick( + private _renderAxisTick( layoutInfo: LayoutInfo, group: graphic.Group, axis: TimelineAxis, @@ -389,18 +416,24 @@ class SliderTimelineView extends TimelineView { // Show all ticks, despite ignoring strategy. const ticks = axis.scale.getTicks(); + this._tickSymbols = []; + // The value is dataIndex, see the costomized scale. - each(ticks, function (value) { + each(ticks, (value) => { const tickCoord = axis.dataToCoord(value); const itemModel = data.getItemModel(value); const itemStyleModel = itemModel.getModel('itemStyle'); const hoverStyleModel = itemModel.getModel(['emphasis', 'itemStyle']); + const progressStyleModel = itemModel.getModel(['progress', 'itemStyle']); + const symbolOpt = { position: [tickCoord, 0], onclick: bind(this._changeTimeline, this, value) }; const el = giveSymbol(itemModel, itemStyleModel, group, symbolOpt); el.ensureState('emphasis').style = hoverStyleModel.getItemStyle(); + el.ensureState('progress').style = progressStyleModel.getItemStyle(); + enableHoverEmphasis(el); const ecData = graphic.getECData(el); @@ -412,13 +445,11 @@ class SliderTimelineView extends TimelineView { ecData.dataIndex = ecData.dataModel = null; } - }, this); + this._tickSymbols.push(el); + }); } - /** - * @private - */ - _renderAxisLabel( + private _renderAxisLabel( layoutInfo: LayoutInfo, group: graphic.Group, axis: TimelineAxis, @@ -433,13 +464,17 @@ class SliderTimelineView extends TimelineView { const data = timelineModel.getData(); const labels = axis.getViewLabels(); - each(labels, function (labelItem) { + this._tickLabels = []; + + each(labels, (labelItem) => { // The tickValue is dataIndex, see the costomized scale. const dataIndex = labelItem.tickValue; const itemModel = data.getItemModel(dataIndex); const normalLabelModel = itemModel.getModel('label'); const hoverLabelModel = itemModel.getModel(['emphasis', 'label']); + const progressLabelModel = itemModel.getModel(['progress', 'label']); + const tickCoord = axis.dataToCoord(labelItem.tickValue); const textEl = new graphic.Text({ x: tickCoord, @@ -455,17 +490,19 @@ class SliderTimelineView extends TimelineView { }); textEl.ensureState('emphasis').style = createTextStyle(hoverLabelModel); + textEl.ensureState('progress').style = createTextStyle(progressLabelModel); group.add(textEl); enableHoverEmphasis(textEl); - }, this); + labelDataIndexStore(textEl).dataIndex = dataIndex; + + this._tickLabels.push(textEl); + + }); } - /** - * @private - */ - _renderControl( + private _renderControl( layoutInfo: LayoutInfo, group: graphic.Group, axis: TimelineAxis, @@ -476,36 +513,40 @@ class SliderTimelineView extends TimelineView { const itemStyle = timelineModel.getModel('controlStyle').getItemStyle(); const hoverStyle = timelineModel.getModel(['emphasis', 'controlStyle']).getItemStyle(); - const rect = [0, -controlSize / 2, controlSize, controlSize]; const playState = timelineModel.getPlayState(); const inverse = timelineModel.get('inverse', true); makeBtn( layoutInfo.nextBtnPosition, - 'nextIcon', + 'next', bind(this._changeTimeline, this, inverse ? '-' : '+') ); makeBtn( layoutInfo.prevBtnPosition, - 'prevIcon', + 'prev', bind(this._changeTimeline, this, inverse ? '+' : '-') ); makeBtn( layoutInfo.playPosition, - (playState ? 'stopIcon' : 'playIcon'), + (playState ? 'stop' : 'play'), bind(this._handlePlayClick, this, !playState), true ); function makeBtn( position: number[], - iconPath: ControlIconName, + iconName: ControlName, onclick: () => void, willRotate?: boolean ) { if (!position) { return; } + const iconSize = parsePercent( + retrieve2(timelineModel.get(['controlStyle', iconName + 'BtnSize' as any]), controlSize), + controlSize + ); + const rect = [0, -iconSize / 2, iconSize, iconSize]; const opt = { position: position, origin: [controlSize / 2, 0], @@ -514,14 +555,14 @@ class SliderTimelineView extends TimelineView { style: itemStyle, onclick: onclick }; - const btn = makeControlIcon(timelineModel, iconPath, rect, opt); + const btn = makeControlIcon(timelineModel, iconName + 'Icon' as ControlIconName, rect, opt); btn.ensureState('emphasis').style = hoverStyle; group.add(btn); enableHoverEmphasis(btn); } } - _renderCurrentPointer( + private _renderCurrentPointer( layoutInfo: LayoutInfo, group: graphic.Group, axis: TimelineAxis, @@ -538,10 +579,10 @@ class SliderTimelineView extends TimelineView { pointer.draggable = true; pointer.drift = bind(me._handlePointerDrag, me); pointer.ondragend = bind(me._handlePointerDragend, me); - pointerMoveTo(pointer, currentIndex, axis, timelineModel, true); + pointerMoveTo(pointer, me._progressLine, currentIndex, axis, timelineModel, true); }, onUpdate(pointer: TimelineSymbol) { - pointerMoveTo(pointer, currentIndex, axis, timelineModel); + pointerMoveTo(pointer, me._progressLine, currentIndex, axis, timelineModel); } }; @@ -551,7 +592,7 @@ class SliderTimelineView extends TimelineView { ); } - _handlePlayClick(nextState: boolean) { + private _handlePlayClick(nextState: boolean) { this._clearTimer(); this.api.dispatchAction({ type: 'timelinePlayChange', @@ -560,16 +601,16 @@ class SliderTimelineView extends TimelineView { } as TimelinePlayChangePayload); } - _handlePointerDrag(dx: number, dy: number, e: ZRElementEvent) { + private _handlePointerDrag(dx: number, dy: number, e: ZRElementEvent) { this._clearTimer(); this._pointerChangeTimeline([e.offsetX, e.offsetY]); } - _handlePointerDragend(e: ZRElementEvent) { + private _handlePointerDragend(e: ZRElementEvent) { this._pointerChangeTimeline([e.offsetX, e.offsetY], true); } - _pointerChangeTimeline(mousePos: number[], trigger?: boolean) { + private _pointerChangeTimeline(mousePos: number[], trigger?: boolean) { let toCoord = this._toAxisCoord(mousePos)[0]; const axis = this._axis; @@ -581,6 +622,9 @@ class SliderTimelineView extends TimelineView { this._currentPointer.x = toCoord; this._currentPointer.markRedraw(); + this._progressLine.shape.x2 = toCoord; + this._progressLine.dirty(); + const targetDataIndex = this._findNearestTick(toCoord); const timelineModel = this.model; @@ -592,7 +636,7 @@ class SliderTimelineView extends TimelineView { } } - _doPlayStop() { + private _doPlayStop() { this._clearTimer(); if (this.model.getPlayState()) { @@ -610,12 +654,12 @@ class SliderTimelineView extends TimelineView { } } - _toAxisCoord(vertex: number[]) { + private _toAxisCoord(vertex: number[]) { const trans = this._mainGroup.getLocalTransform(); return graphic.applyTransform(vertex, trans, true); } - _findNearestTick(axisCoord: number) { + private _findNearestTick(axisCoord: number) { const data = this.model.getData(); let dist = Infinity; let targetDataIndex; @@ -633,14 +677,14 @@ class SliderTimelineView extends TimelineView { return targetDataIndex; } - _clearTimer() { + private _clearTimer() { if (this._timer) { clearTimeout(this._timer); this._timer = null; } } - _changeTimeline(nextIndex: number | '+' | '-') { + private _changeTimeline(nextIndex: number | '+' | '-') { const currentIndex = this.model.getCurrentIndex(); if (nextIndex === '+') { @@ -657,6 +701,23 @@ class SliderTimelineView extends TimelineView { } as TimelineChangePayload); } + private _updateTicksStatus() { + const currentIndex = this.model.getCurrentIndex(); + const tickSymbols = this._tickSymbols; + const tickLabels = this._tickLabels; + if (!(tickSymbols || tickLabels)) { + return; + } + + for (let i = 0; i < tickSymbols.length; i++) { + tickSymbols && tickSymbols[i] + && tickSymbols[i].toggleState('progress', i < currentIndex); + } + for (let i = 0; i < tickLabels.length; i++) { + tickLabels && tickLabels[i] + && tickLabels[i].toggleState('progress', labelDataIndexStore(tickLabels[i]).dataIndex <= currentIndex); + } + } } function createScaleByModel(model: SliderTimelineModel, axisType?: string): Scale { @@ -782,6 +843,7 @@ function giveSymbol( function pointerMoveTo( pointer: TimelineSymbol, + progressLine: graphic.Line, dataIndex: number, axis: TimelineAxis, timelineModel: SliderTimelineModel, @@ -795,20 +857,28 @@ function pointerMoveTo( const toCoord = axis.dataToCoord(timelineModel.getData().get('value', dataIndex)); if (noAnimation || !pointerModel.get('animation', true)) { - pointer.x = toCoord; - pointer.y = 0; + pointer.attr({ + x: toCoord, + y: 0 + }); + progressLine && progressLine.attr({ + shape: { x2: toCoord } + }); } else { + const animationCfg = { + duration: pointerModel.get('animationDuration', true), + easing: pointerModel.get('animationEasing', true) + }; pointer.stopAnimation(null, true); pointer.animateTo({ x: toCoord, y: 0 - }, { - duration: pointerModel.get('animationDuration', true), - easing: pointerModel.get('animationEasing', true) - }); + }, animationCfg); + progressLine && progressLine.animateTo({ + shape: { x2: toCoord } + }, animationCfg); } - pointer.markRedraw(); } diff --git a/src/component/timeline/TimelineModel.ts b/src/component/timeline/TimelineModel.ts index 385854c116..a32218acb5 100644 --- a/src/component/timeline/TimelineModel.ts +++ b/src/component/timeline/TimelineModel.ts @@ -52,6 +52,12 @@ export interface TimelineControlStyle extends ItemStyleOption { stopIcon?: string prevIcon?: string nextIcon?: string + + // Can be a percent value relative to itemSize + playBtnSize: number | string + stopBtnSize: number | string + nextBtnSize: number | string + prevBtnSize: number | string } export interface TimelineCheckpointStyle extends ItemStyleOption, @@ -84,6 +90,13 @@ export interface TimelineDataItemOption extends SymbolOptionMixin { checkpointStyle?: TimelineCheckpointStyle } + // Style in progress + progress?: { + lineStyle?: TimelineLineStyleOption + itemStyle?: ItemStyleOption + label?: TimelineLabelOption + } + tooltip?: boolean } @@ -139,6 +152,14 @@ export interface TimelineOption extends ComponentOption, BoxLayoutOptionMixin, S label?: TimelineLabelOption } + + // Style in progress + progress?: { + lineStyle?: TimelineLineStyleOption + itemStyle?: ItemStyleOption + label?: TimelineLabelOption + } + data?: (OptionDataValue | TimelineDataItemOption)[] } class TimelineModel extends ComponentModel { diff --git a/src/component/visualMap/ContinuousModel.ts b/src/component/visualMap/ContinuousModel.ts index ab27dcaef3..5759d5fc4b 100644 --- a/src/component/visualMap/ContinuousModel.ts +++ b/src/component/visualMap/ContinuousModel.ts @@ -23,6 +23,7 @@ import * as numberUtil from '../../util/number'; import ComponentModel from '../../model/Component'; import { VisualMappingOption } from '../../visual/VisualMapping'; import { inheritDefaultOption } from '../../util/component'; +import { ItemStyleOption } from '../../util/types'; // Constant const DEFAULT_BAR_BOUND = [20, 140]; @@ -62,7 +63,21 @@ export interface ContinousVisualMapOption extends VisualMapOption { * Whether trigger hoverLink when hover handle. * If not specified, follow the value of `realtime`. */ - hoverLinkOnHandle?: boolean + hoverLinkOnHandle?: boolean, + + handleIcon?: string, + // Percent of the item width + handleSize?: string | number, + handleStyle?: ItemStyleOption + + indicatorIcon?: string, + // Percent of the item width + indicatorSize?: string | number, + indicatorStyle?: ItemStyleOption + + emphasis?: { + handleStyle?: ItemStyleOption + } } class ContinuousModel extends VisualMapModel { @@ -131,7 +146,7 @@ class ContinuousModel extends VisualMapModel { zrUtil.each(this.stateList, function (state: VisualState) { const symbolSize = this.option.controller[state].symbolSize; if (symbolSize && symbolSize[0] !== symbolSize[1]) { - symbolSize[0] = 0; // For good looking. + symbolSize[0] = symbolSize[1] / 3; // For good looking. } }, this); } @@ -264,7 +279,34 @@ class ContinuousModel extends VisualMapModel { align: 'auto', // 'auto', 'left', 'right', 'top', 'bottom' calculable: false, hoverLink: true, - realtime: true + realtime: true, + + handleIcon: 'path://M-11.39,9.77h0a3.5,3.5,0,0,1-3.5,3.5h-22a3.5,3.5,0,0,1-3.5-3.5h0a3.5,3.5,0,0,1,3.5-3.5h22A3.5,3.5,0,0,1-11.39,9.77Z', + handleSize: '120%', + + handleStyle: { + borderColor: '#fff', + borderWidth: 1 + }, + + indicatorIcon: 'circle', + indicatorSize: '50%', + indicatorStyle: { + borderColor: '#fff', + borderWidth: 2, + shadowBlur: 2, + shadowOffsetX: 1, + shadowOffsetY: 1, + shadowColor: 'rgba(0,0,0,0.2)' + } + // emphasis: { + // handleStyle: { + // shadowBlur: 3, + // shadowOffsetX: 1, + // shadowOffsetY: 1, + // shadowColor: 'rgba(0,0,0,0.2)' + // } + // } }) as ContinousVisualMapOption; } diff --git a/src/component/visualMap/ContinuousView.ts b/src/component/visualMap/ContinuousView.ts index 7cafb0bbbf..211aec752f 100644 --- a/src/component/visualMap/ContinuousView.ts +++ b/src/component/visualMap/ContinuousView.ts @@ -34,6 +34,10 @@ import ExtensionAPI from '../../ExtensionAPI'; import Element, { ElementEvent } from 'zrender/src/Element'; import { TextVerticalAlign, TextAlign } from 'zrender/src/core/types'; import { ColorString, Payload } from '../../util/types'; +import { parsePercent } from 'zrender/src/contain/text'; +import { setAsHighDownDispatcher, enterBlur, leaveBlur } from '../../util/states'; +import { createSymbol } from '../../util/symbol'; +import ZRImage from 'zrender/src/graphic/Image'; const linearMap = numberUtil.linearMap; const each = zrUtil.each; @@ -48,16 +52,16 @@ const HOVER_LINK_OUT = 6; type Orient = VisualMapModel['option']['orient']; type ShapeStorage = { - handleThumbs: graphic.Polygon[] + handleThumbs: graphic.Path[] handleLabelPoints: number[][] handleLabels: graphic.Text[] inRange: graphic.Polygon outOfRange: graphic.Polygon - barGroup: graphic.Group + mainGroup: graphic.Group - indicator: graphic.Polygon + indicator: graphic.Path indicatorLabel: graphic.Text indicatorLabelPoint: number[] }; @@ -101,6 +105,8 @@ class ContinuousView extends VisualMapView { private _hovering: boolean; + private _firstShowIndicator: boolean; + doRender( visualMapModel: ContinuousModel, @@ -159,7 +165,7 @@ class ContinuousView extends VisualMapView { const textGap = visualMapModel.get('textGap'); const itemSize = visualMapModel.itemSize; - const barGroup = this._shapes.barGroup; + const barGroup = this._shapes.mainGroup; const position = this._applyTransform( [ itemSize[0] / 2, @@ -194,17 +200,31 @@ class ContinuousView extends VisualMapView { const orient = this._orient; const useHandle = this._useHandle; const itemAlign = helper.getItemAlign(visualMapModel, this.api, itemSize); - const barGroup = shapes.barGroup = this._createBarGroup(itemAlign); + const mainGroup = shapes.mainGroup = this._createBarGroup(itemAlign); + + const gradientBarGroup = new graphic.Group(); + mainGroup.add(gradientBarGroup); // Bar - barGroup.add(shapes.outOfRange = createPolygon()); - barGroup.add(shapes.inRange = createPolygon( + gradientBarGroup.add(shapes.outOfRange = createPolygon()); + gradientBarGroup.add(shapes.inRange = createPolygon( null, useHandle ? getCursor(this._orient) : null, zrUtil.bind(this._dragHandle, this, 'all', false), zrUtil.bind(this._dragHandle, this, 'all', true) )); + // A border radius clip. + gradientBarGroup.setClipPath(new graphic.Rect({ + shape: { + x: 0, + y: 0, + width: itemSize[0], + height: itemSize[1], + r: 3 + } + })); + const textRect = visualMapModel.textStyleModel.getTextRect('国'); const textSize = mathMax(textRect.width, textRect.height); @@ -214,17 +234,18 @@ class ContinuousView extends VisualMapView { shapes.handleLabels = []; shapes.handleLabelPoints = []; - this._createHandle(barGroup, 0, itemSize, textSize, orient); - this._createHandle(barGroup, 1, itemSize, textSize, orient); + this._createHandle(visualMapModel, mainGroup, 0, itemSize, textSize, orient); + this._createHandle(visualMapModel, mainGroup, 1, itemSize, textSize, orient); } - this._createIndicator(barGroup, itemSize, textSize, orient); + this._createIndicator(visualMapModel, mainGroup, itemSize, textSize, orient); - targetGroup.add(barGroup); + targetGroup.add(mainGroup); } private _createHandle( - barGroup: graphic.Group, + visualMapModel: ContinuousModel, + mainGroup: graphic.Group, handleIndex: 0 | 1, itemSize: number[], textSize: number, @@ -232,14 +253,35 @@ class ContinuousView extends VisualMapView { ) { const onDrift = zrUtil.bind(this._dragHandle, this, handleIndex, false); const onDragEnd = zrUtil.bind(this._dragHandle, this, handleIndex, true); - const handleThumb = createPolygon( - createHandlePoints(handleIndex, textSize), - getCursor(this._orient), - onDrift, - onDragEnd + const handleSize = parsePercent(visualMapModel.get('handleSize'), itemSize[0]); + const handleThumb = createSymbol( + visualMapModel.get('handleIcon'), + -handleSize / 2, -handleSize / 2, handleSize, handleSize, + null, true ); - handleThumb.x = itemSize[0]; - barGroup.add(handleThumb); + const cursor = getCursor(this._orient); + handleThumb.attr({ + cursor: cursor, + draggable: true, + drift: onDrift, + ondragend: onDragEnd, + onmousemove(e) { + eventTool.stop(e.event); + } + }); + handleThumb.x = itemSize[0] / 2; + + handleThumb.useStyle(visualMapModel.getModel('handleStyle').getItemStyle()); + (handleThumb as graphic.Path).setStyle({ + strokeNoScale: true, + strokeFirst: true + }); + (handleThumb as graphic.Path).style.lineWidth *= 2; + + handleThumb.ensureState('emphasis').style = visualMapModel.getModel(['emphasis', 'handleStyle']).getItemStyle(); + setAsHighDownDispatcher(handleThumb, true); + + mainGroup.add(handleThumb); // Text is always horizontal layout but should not be effected by // transform (orient/inverse). So label is built separately but not @@ -247,6 +289,7 @@ class ContinuousView extends VisualMapView { // group (according to handleLabelPoint) but not barGroup. const textStyleModel = this.visualMapModel.textStyleModel; const handleLabel = new graphic.Text({ + cursor: cursor, draggable: true, drift: onDrift, onmousemove(e) { @@ -260,16 +303,14 @@ class ContinuousView extends VisualMapView { fill: textStyleModel.getTextColor() } }); + handleLabel.ensureState('blur').style = { + opacity: 0.1 + }; + handleLabel.stateTransition = { duration: 200 }; + this.group.add(handleLabel); - const handleLabelPoint = [ - orient === 'horizontal' - ? textSize / 2 - : textSize * 1.5, - orient === 'horizontal' - ? (handleIndex === 0 ? -(textSize * 1.5) : (textSize * 1.5)) - : (handleIndex === 0 ? -textSize / 2 : textSize / 2) - ]; + const handleLabelPoint = [handleSize, 0]; const shapes = this._shapes; shapes.handleThumbs[handleIndex] = handleThumb; @@ -278,15 +319,39 @@ class ContinuousView extends VisualMapView { } private _createIndicator( - barGroup: graphic.Group, + visualMapModel: ContinuousModel, + mainGroup: graphic.Group, itemSize: number[], textSize: number, orient: Orient ) { - const indicator = createPolygon([[0, 0]], 'move'); - indicator.x = itemSize[0]; - indicator.attr({invisible: true, silent: true}); - barGroup.add(indicator); + const scale = parsePercent(visualMapModel.get('indicatorSize'), itemSize[0]); + const indicator = createSymbol( + visualMapModel.get('indicatorIcon'), + -scale / 2, -scale / 2, scale, scale, + null, true + ); + indicator.attr({ + cursor: 'move', + invisible: true, + silent: true, + x: itemSize[0] / 2 + }); + const indicatorStyle = visualMapModel.getModel('indicatorStyle').getItemStyle(); + if (indicator instanceof ZRImage) { + const pathStyle = indicator.style; + indicator.useStyle(zrUtil.extend({ + // TODO other properties like x, y ? + image: pathStyle.image, + x: pathStyle.x, y: pathStyle.y, + width: pathStyle.width, height: pathStyle.height + }, indicatorStyle)); + } + else { + indicator.useStyle(indicatorStyle); + } + + mainGroup.add(indicator); const textStyleModel = this.visualMapModel.textStyleModel; const indicatorLabel = new graphic.Text({ @@ -301,7 +366,7 @@ class ContinuousView extends VisualMapView { this.group.add(indicatorLabel); const indicatorLabelPoint = [ - orient === 'horizontal' ? textSize / 2 : HOVER_LINK_OUT + 3, + (orient === 'horizontal' ? textSize / 2 : HOVER_LINK_OUT) + itemSize[0] / 2, 0 ]; @@ -309,6 +374,8 @@ class ContinuousView extends VisualMapView { shapes.indicator = indicator; shapes.indicatorLabel = indicatorLabel; shapes.indicatorLabelPoint = indicatorLabelPoint; + + this._firstShowIndicator = true; } private _dragHandle( @@ -326,9 +393,10 @@ class ContinuousView extends VisualMapView { if (!isEnd) { // Transform dx, dy to bar coordination. - const vertex = this._applyTransform([dx as number, dy], this._shapes.barGroup, true) as number[]; + const vertex = this._applyTransform([dx as number, dy], this._shapes.mainGroup, true) as number[]; this._updateInterval(handleIndex, vertex[1]); + this._hideIndicator(); // Considering realtime, update view should be executed // before dispatch action. this._updateView(); @@ -526,12 +594,20 @@ class ContinuousView extends VisualMapView { const visualMapModel = this.visualMapModel; const handleThumbs = shapes.handleThumbs; const handleLabels = shapes.handleLabels; + const itemSize = visualMapModel.itemSize; + const dataExtent = visualMapModel.getExtent(); each([0, 1], function (handleIndex) { const handleThumb = handleThumbs[handleIndex]; handleThumb.setStyle('fill', visualInRange.handlesColor[handleIndex]); handleThumb.y = handleEnds[handleIndex]; + const val = linearMap(handleEnds[handleIndex], [0, itemSize[1]], dataExtent, true); + const symbolSize = this.getControllerVisual(val, 'symbolSize') as number; + + handleThumb.scaleX = handleThumb.scaleY = symbolSize / itemSize[0]; + handleThumb.x = itemSize[0] - symbolSize / 2; + // Update handle label position. const textPoint = graphic.applyTransform( shapes.handleLabelPoints[handleIndex], @@ -542,12 +618,10 @@ class ContinuousView extends VisualMapView { y: textPoint[1], text: visualMapModel.formatValueText(this._dataInterval[handleIndex]), verticalAlign: 'middle', - align: this._applyTransform( - this._orient === 'horizontal' - ? (handleIndex === 0 ? 'bottom' : 'top') - : 'left', - shapes.barGroup - ) as TextAlign + align: this._orient === 'vertical' ? this._applyTransform( + 'left', + shapes.mainGroup + ) as TextAlign : 'center' }); }, this); } @@ -562,7 +636,6 @@ class ContinuousView extends VisualMapView { const dataExtent = visualMapModel.getExtent(); const itemSize = visualMapModel.itemSize; const sizeExtent = [0, itemSize[1]]; - const pos = linearMap(cursorValue, dataExtent, sizeExtent, true); const shapes = this._shapes; const indicator = shapes.indicator; @@ -570,17 +643,18 @@ class ContinuousView extends VisualMapView { return; } - indicator.y = pos; indicator.attr('invisible', false); - indicator.setShape('points', createIndicatorPoints( - !!rangeSymbol, halfHoverLinkSize, pos, itemSize[1] - )); const opts = {convertOpacityToAlpha: true}; const color = this.getControllerVisual(cursorValue, 'color', opts) as ColorString; - indicator.setStyle('fill', color); + const symbolSize = this.getControllerVisual(cursorValue, 'symbolSize') as number; + const y = linearMap(cursorValue, dataExtent, sizeExtent, true); + const x = itemSize[0] - symbolSize / 2; + const oldIndicatorPos = { x: indicator.x, y: indicator.y }; // Update handle label position. + indicator.y = y; + indicator.x = x; const textPoint = graphic.applyTransform( shapes.indicatorLabelPoint, graphic.getTransform(indicator, this.group) @@ -588,21 +662,60 @@ class ContinuousView extends VisualMapView { const indicatorLabel = shapes.indicatorLabel; indicatorLabel.attr('invisible', false); - const align = this._applyTransform('left', shapes.barGroup); + const align = this._applyTransform('left', shapes.mainGroup); const orient = this._orient; const isHorizontal = orient === 'horizontal'; indicatorLabel.setStyle({ text: (rangeSymbol ? rangeSymbol : '') + visualMapModel.formatValueText(textValue), verticalAlign: isHorizontal ? align as TextVerticalAlign : 'middle', - align: isHorizontal ? 'center' : align as TextAlign, - x: textPoint[0], - y: textPoint[1] + align: isHorizontal ? 'center' : align as TextAlign }); + + const indicatorNewProps = { + x: x, + y: y, + style: { + fill: color + } + }; + const labelNewProps = { + style: { + x: textPoint[0], + y: textPoint[1] + } + }; + + if (visualMapModel.ecModel.isAnimationEnabled() && !this._firstShowIndicator) { + const animationCfg = { + duration: 100, + easing: 'cubicInOut', + additive: true + } as const; + indicator.x = oldIndicatorPos.x; + indicator.y = oldIndicatorPos.y; + indicator.animateTo(indicatorNewProps, animationCfg); + indicatorLabel.animateTo(labelNewProps, animationCfg); + } + else { + indicator.attr(indicatorNewProps); + indicatorLabel.attr(labelNewProps); + } + + this._firstShowIndicator = false; + + const handleLabels = this._shapes.handleLabels; + if (handleLabels) { + for (let i = 0; i < handleLabels.length; i++) { + // Fade out handle labels. + // TODO not do twice. + enterBlur(handleLabels[i]); + } + } } private _enableHoverLinkToSeries() { const self = this; - this._shapes.barGroup + this._shapes.mainGroup .on('mousemove', function (e) { self._hovering = true; @@ -610,7 +723,7 @@ class ContinuousView extends VisualMapView { if (!self._dragging) { const itemSize = self.visualMapModel.itemSize; const pos = self._applyTransform( - [e.offsetX, e.offsetY], self._shapes.barGroup, true, true + [e.offsetX, e.offsetY], self._shapes.mainGroup, true, true ); // For hover link show when hover handle, which might be // below or upper than sizeExtent. @@ -727,6 +840,14 @@ class ContinuousView extends VisualMapView { const shapes = this._shapes; shapes.indicator && shapes.indicator.attr('invisible', true); shapes.indicatorLabel && shapes.indicatorLabel.attr('invisible', true); + + const handleLabels = this._shapes.handleLabels; + if (handleLabels) { + for (let i = 0; i < handleLabels.length; i++) { + // Fade out handle labels. + leaveBlur(handleLabels[i]); + } + } } private _clearHoverLinkToSeries() { @@ -805,24 +926,6 @@ function createPolygon( }); } -function createHandlePoints(handleIndex: 0 | 1, textSize: number) { - return handleIndex === 0 - ? [[0, 0], [textSize, 0], [textSize, -textSize]] - : [[0, 0], [textSize, 0], [textSize, textSize]]; -} - -function createIndicatorPoints(isRange: boolean, halfHoverLinkSize: number, pos: number, extentMax: number) { - return isRange - ? [ // indicate range - [0, -mathMin(halfHoverLinkSize, mathMax(pos, 0))], - [HOVER_LINK_OUT, 0], - [0, mathMin(halfHoverLinkSize, mathMax(extentMax - pos, 0))] - ] - : [ // indicate single value - [0, 0], [5, -5], [5, 5] - ]; -} - function getHalfHoverLinkSize(visualMapModel: ContinuousModel, dataExtent: number[], sizeExtent: number[]) { let halfHoverLinkSize = HOVER_LINK_SIZE / 2; const hoverLinkDataSize = visualMapModel.get('hoverLinkDataSize'); diff --git a/src/echarts.ts b/src/echarts.ts index 64e4d89702..3e745dfe3d 100644 --- a/src/echarts.ts +++ b/src/echarts.ts @@ -1360,7 +1360,7 @@ class ECharts extends Eventful { // If dispatchAction before setOption, do nothing. ecModel && ecModel.eachComponent(condition, function (model) { if (!excludeSeriesIdMap || excludeSeriesIdMap.get(model.id) == null) { - if (isHighDownPayload(payload)) { + if (isHighDownPayload(payload) && !payload.notBlur) { if (model instanceof SeriesModel) { toggleSeriesBlurStateFromPayload(model, payload, ecIns); } @@ -1799,6 +1799,7 @@ class ECharts extends Eventful { if (dispatcher) { const ecData = getECData(dispatcher); // Try blur all in the related series. Then emphasis the hoverred. + // TODO. progressive mode. toggleSeriesBlurState( ecData.seriesIndex, ecData.focus, ecData.blurScope, ecIns, true ); @@ -1943,8 +1944,7 @@ class ECharts extends Eventful { // If use hover layer - // TODO - // updateHoverLayerStatus(ecIns, ecModel); + updateHoverLayerStatus(ecIns, ecModel); // Add aria aria(ecIns._zr.dom, ecModel); @@ -1968,45 +1968,50 @@ class ECharts extends Eventful { } ecIns.getZr().storage.traverse(function (el: ECElement) { - const newStates = []; - const oldStates = el.currentStates; - // Not applied on removed elements, it may still in fading. if (graphic.isElementRemoved(el)) { return; } - - // Keep other states. - for (let i = 0; i < oldStates.length; i++) { - const stateName = oldStates[i]; - if (!(stateName === 'emphasis' || stateName === 'blur' || stateName === 'select')) { - newStates.push(stateName); - } - } - - // Only use states when it's exists. - if (el.selected && el.states.select) { - newStates.push('select'); - } - if (el.hoverState === HOVER_STATE_EMPHASIS && el.states.emphasis) { - newStates.push('emphasis'); - } - else if (el.hoverState === HOVER_STATE_BLUR && el.states.blur) { - newStates.push('blur'); - } - el.useStates(newStates); + applyElementStates(el); }); ecIns[STATUS_NEEDS_UPDATE_KEY] = false; }; + function applyElementStates(el: ECElement) { + const newStates = []; + + const oldStates = el.currentStates; + // Keep other states. + for (let i = 0; i < oldStates.length; i++) { + const stateName = oldStates[i]; + if (!(stateName === 'emphasis' || stateName === 'blur' || stateName === 'select')) { + newStates.push(stateName); + } + } + + // Only use states when it's exists. + if (el.selected && el.states.select) { + newStates.push('select'); + } + if (el.hoverState === HOVER_STATE_EMPHASIS && el.states.emphasis) { + newStates.push('emphasis'); + } + else if (el.hoverState === HOVER_STATE_BLUR && el.states.blur) { + newStates.push('blur'); + } + el.useStates(newStates); + } + function updateHoverLayerStatus(ecIns: ECharts, ecModel: GlobalModel): void { const zr = ecIns._zr; const storage = zr.storage; let elCount = 0; storage.traverse(function (el) { - elCount++; + if (!el.isGroup) { + elCount++; + } }); if (elCount > ecModel.get('hoverLayerThreshold') && !env.node) { @@ -2017,8 +2022,9 @@ class ECharts extends Eventful { const chartView = ecIns._chartsMap[seriesModel.__viewId]; if (chartView.__alive) { chartView.group.traverse(function (el: ECElement) { - // Don't switch back. - el.useHoverLayer = true; + if (el.states.emphasis) { + el.states.emphasis.hoverLayer = true; + } }); } }); @@ -2026,7 +2032,7 @@ class ECharts extends Eventful { }; /** - * Update chart progressive and blend. + * Update chart and blend. */ function updateBlend(seriesModel: SeriesModel, chartView: ChartView): void { const blendMode = seriesModel.get('blendMode') || null; @@ -2158,14 +2164,7 @@ class ECharts extends Eventful { // The use higlighted and selected flag to toggle states. if (el.__dirty) { - const states = []; - if ((el as ECElement).selected) { - states.push('select'); - } - if ((el as ECElement).hoverState) { - states.push('emphasis'); - } - el.useStates(states); + applyElementStates(el); } } }); diff --git a/src/label/labelStyle.ts b/src/label/labelStyle.ts index 731d1bb4f2..449e9f2a43 100644 --- a/src/label/labelStyle.ts +++ b/src/label/labelStyle.ts @@ -16,6 +16,8 @@ import { import GlobalModel from '../model/Global'; import { isFunction, retrieve2, extend, keys, trim } from 'zrender/src/core/util'; import { SPECIAL_STATES, DISPLAY_STATES } from '../util/states'; +import { __DEV__ } from '../config'; +import { deprecateReplaceLog } from '../util/log'; type TextCommonParams = { /** @@ -30,6 +32,11 @@ type TextCommonParams = { defaultOutsidePosition?: LabelOption['position'] + /** + * If support legacy 'auto' for 'inherit' usage. + */ + // supportLegacyAuto?: boolean + textStyle?: ZRStyleProps }; const EMPTY_OBJ = {}; @@ -406,7 +413,12 @@ function setTokenTextStyle( const inheritColor = opt && opt.inheritColor; let fillColor = textStyleModel.getShallow('color'); let strokeColor = textStyleModel.getShallow('textBorderColor'); - if (fillColor === 'inherit') { + if (fillColor === 'inherit' || fillColor === 'auto') { + if (__DEV__) { + if (fillColor === 'auto') { + deprecateReplaceLog('color: \'auto\'', 'color: \'inherit\''); + } + } if (inheritColor) { fillColor = inheritColor; } @@ -414,12 +426,17 @@ function setTokenTextStyle( fillColor = null; } } - if (strokeColor === 'inherit' && inheritColor) { + if (strokeColor === 'inherit' || (strokeColor === 'auto')) { + if (__DEV__) { + if (strokeColor === 'auto') { + deprecateReplaceLog('color: \'auto\'', 'color: \'inherit\''); + } + } if (inheritColor) { strokeColor = inheritColor; } else { - strokeColor = inheritColor; + strokeColor = null; } } fillColor = fillColor || globalTextStyle.color; @@ -465,12 +482,6 @@ function setTokenTextStyle( } } if (!isBlock || !opt.disableBox) { - if (textStyle.backgroundColor === 'auto' && inheritColor) { - textStyle.backgroundColor = inheritColor; - } - if (textStyle.borderColor === 'auto' && inheritColor) { - textStyle.borderColor = inheritColor; - } for (let i = 0; i < TEXT_PROPS_BOX.length; i++) { const key = TEXT_PROPS_BOX[i]; const val = textStyleModel.getShallow(key); @@ -478,6 +489,23 @@ function setTokenTextStyle( (textStyle as any)[key] = val; } } + + if ((textStyle.backgroundColor === 'auto' || textStyle.backgroundColor === 'inherit') && inheritColor) { + if (__DEV__) { + if (textStyle.backgroundColor === 'auto') { + deprecateReplaceLog('backgroundColor: \'auto\'', 'backgroundColor: \'inherit\''); + } + } + textStyle.backgroundColor = inheritColor; + } + if ((textStyle.borderColor === 'auto' || textStyle.borderColor === 'inherit') && inheritColor) { + if (__DEV__) { + if (textStyle.borderColor === 'auto') { + deprecateReplaceLog('borderColor: \'auto\'', 'borderColor: \'inherit\''); + } + } + textStyle.borderColor = inheritColor; + } } } export function getFont( diff --git a/src/layout/barGrid.ts b/src/layout/barGrid.ts index 059d6b4b31..ea3efc690f 100644 --- a/src/layout/barGrid.ts +++ b/src/layout/barGrid.ts @@ -272,8 +272,8 @@ function doCalBarWidthAndOffset(seriesInfoList: LayoutSeriesInfo[]) { bandWidth: bandWidth, remainedWidth: bandWidth, autoWidthCount: 0, - categoryGap: '20%', - gap: '30%', + categoryGap: null, + gap: '20%', stacks: {} }; const stacks = columnsOnAxis.stacks; @@ -320,7 +320,15 @@ function doCalBarWidthAndOffset(seriesInfoList: LayoutSeriesInfo[]) { const stacks = columnsOnAxis.stacks; const bandWidth = columnsOnAxis.bandWidth; - const categoryGap = parsePercent(columnsOnAxis.categoryGap, bandWidth); + let categoryGapPercent = columnsOnAxis.categoryGap; + if (categoryGapPercent == null) { + const columnCount = zrUtil.keys(stacks).length; + // More columns in one group + // the spaces between group is smaller. Or the column will be too thin. + categoryGapPercent = Math.max((35 - columnCount * 4), 15) + '%'; + } + + const categoryGap = parsePercent(categoryGapPercent, bandWidth); const barGapPercent = parsePercent(columnsOnAxis.gap, 1); let remainedWidth = columnsOnAxis.remainedWidth; diff --git a/src/legacy/getTextRect.ts b/src/legacy/getTextRect.ts new file mode 100644 index 0000000000..d656bafb3b --- /dev/null +++ b/src/legacy/getTextRect.ts @@ -0,0 +1,50 @@ +/* +* 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 { Text } from '../util/graphic'; +import { deprecateLog } from '../util/log'; + +type TextStyleProps = Text['style']; +export function getTextRect( + text: TextStyleProps['text'], + font?: TextStyleProps['font'], + align?: TextStyleProps['align'], + verticalAlign?: TextStyleProps['verticalAlign'], + padding?: TextStyleProps['padding'], + rich?: TextStyleProps['rich'], + truncate?: boolean, + lineHeight?: number +) { + deprecateLog('getTextRect is deprecated.'); + + const textEl = new Text({ + style: { + text, + font, + align, + verticalAlign, + padding, + rich, + overflow: truncate ? 'truncate' : null, + lineHeight + } + }); + + return textEl.getBoundingRect(); +} \ No newline at end of file diff --git a/src/util/format.ts b/src/util/format.ts index 8a5b15226b..3317bab87f 100644 --- a/src/util/format.ts +++ b/src/util/format.ts @@ -262,3 +262,6 @@ export function windowOpen(link: string, target: string): void { window.open(link, target); } } + + +export {getTextRect} from '../legacy/getTextRect'; \ No newline at end of file diff --git a/src/util/graphic.ts b/src/util/graphic.ts index 124e8bf2b1..8f039e01c9 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -186,7 +186,7 @@ export function makeImage( rect: ZRRectLike, layout?: 'center' | 'cover' ) { - const path = new ZRImage({ + const zrImg = new ZRImage({ style: { image: imageUrl, x: rect.x, @@ -200,11 +200,11 @@ export function makeImage( width: img.width, height: img.height }; - path.setStyle(centerGraphic(rect, boundingRect)); + zrImg.setStyle(centerGraphic(rect, boundingRect)); } } }); - return path; + return zrImg; } /** diff --git a/src/util/states.ts b/src/util/states.ts index 2f3c474b74..085ec61929 100644 --- a/src/util/states.ts +++ b/src/util/states.ts @@ -5,7 +5,7 @@ import { PatternObject } from 'zrender/src/graphic/Pattern'; import { GradientObject } from 'zrender/src/graphic/Gradient'; import Element, { ElementEvent } from 'zrender/src/Element'; import Model from '../model/Model'; -import { DisplayState, ECElement, ColorString, BlurScope, InnerFocus, Payload, ZRColor } from './types'; +import { DisplayState, ECElement, ColorString, BlurScope, InnerFocus, Payload, ZRColor, HighlightPayload, DownplayPayload } from './types'; import { extend, indexOf, isArrayLike, isObject, keys, isArray, each } from 'zrender/src/core/util'; import { getECData } from './graphic'; import * as colorTool from 'zrender/src/tool/color'; @@ -669,7 +669,7 @@ export function isSelectChangePayload(payload: Payload) { || payloadType === TOGGLE_SELECT_ACTION_TYPE; } -export function isHighDownPayload(payload: Payload) { +export function isHighDownPayload(payload: Payload): payload is HighlightPayload | DownplayPayload { const payloadType = payload.type; return payloadType === HIGHLIGHT_ACTION_TYPE || payloadType === DOWNPLAY_ACTION_TYPE; diff --git a/src/util/symbol.ts b/src/util/symbol.ts index a0bedde91f..8b5dc28483 100644 --- a/src/util/symbol.ts +++ b/src/util/symbol.ts @@ -267,7 +267,7 @@ const symbolShapeMakers: Dictionary = { } }; -const symbolBuildProxies: Dictionary = {}; +export const symbolBuildProxies: Dictionary = {}; zrUtil.each(symbolCtors, function (Ctor, name) { symbolBuildProxies[name] = new Ctor(); }); diff --git a/src/util/types.ts b/src/util/types.ts index 0c7cf8413a..8d6ff52d20 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -99,7 +99,6 @@ export interface ComponentTypeInfo { } export interface ECElement extends Element { - useHoverLayer?: boolean; tooltip?: CommonTooltipOption & { content?: string; formatterParams?: unknown; @@ -146,6 +145,16 @@ export interface Payload extends PayloadItem { batch?: PayloadItem[]; } +export interface HighlightPayload extends Payload { + type: 'highlight'; + notBlur?: boolean +} + +export interface DownplayPayload extends Payload { + type: 'downplay'; + notBlur?: boolean +} + // Payload includes override anmation info export interface PayloadAnimationPart { duration?: number diff --git a/test/boxplot.html b/test/boxplot.html index 37e7bd6354..fecdb06eb2 100644 --- a/test/boxplot.html +++ b/test/boxplot.html @@ -125,7 +125,7 @@ } ], legend: { - data: ['line', 'line2', 'line3'] + left: 'right' }, tooltip: { trigger: 'item', diff --git a/test/timeline-finance.html b/test/timeline-finance.html index 29e9c44a0a..3006f0126a 100644 --- a/test/timeline-finance.html +++ b/test/timeline-finance.html @@ -83,12 +83,12 @@ axisType: 'category', // realtime: false, // loop: false, - autoPlay: true, + autoPlay: false, // currentIndex: 2, playInterval: 1000, controlStyle: { - showNextBtn: false, - showPrevBtn: false, + showNextBtn: true, + showPrevBtn: true, position: 'left' }, data: [ diff --git a/test/visualMap-scatter-symbolSize.html b/test/visualMap-scatter-symbolSize.html index 1c0144117b..f3243befbc 100644 --- a/test/visualMap-scatter-symbolSize.html +++ b/test/visualMap-scatter-symbolSize.html @@ -109,6 +109,7 @@ calculable: true, inverse: true, dimension: 'value', + top: 'center', inRange: { symbolSize: [10, 180] }