From e6c0f31f271cbd92c3d3b92e95a09183bbe85cb3 Mon Sep 17 00:00:00 2001 From: pissang Date: Wed, 15 Jul 2020 20:08:09 +0800 Subject: [PATCH] feat(state): add focus ascendant, descendant on tree --- src/chart/helper/Symbol.ts | 20 ++- src/chart/helper/SymbolDraw.ts | 3 +- src/chart/sunburst/SunburstPiece.ts | 23 +--- src/chart/tree/TreeView.ts | 201 +++++++++++++--------------- src/chart/treemap/TreemapView.ts | 21 +-- src/data/Tree.ts | 18 +++ test/hoverFocus.html | 22 ++- 7 files changed, 145 insertions(+), 163 deletions(-) diff --git a/src/chart/helper/Symbol.ts b/src/chart/helper/Symbol.ts index 296c9a6f6..dece353d3 100644 --- a/src/chart/helper/Symbol.ts +++ b/src/chart/helper/Symbol.ts @@ -32,6 +32,11 @@ import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; type ECSymbol = ReturnType; +interface SymbolOpts { + useNameLabel?: boolean + symbolInnerColor?: string +} + class Symbol extends graphic.Group { private _seriesModel: SeriesModel; @@ -46,9 +51,9 @@ class Symbol extends graphic.Group { private _z2: number; - constructor(data: List, idx: number, seriesScope?: SymbolDrawSeriesScope) { + constructor(data: List, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) { super(); - this.updateData(data, idx, seriesScope); + this.updateData(data, idx, seriesScope, opts); } _createSymbol( @@ -139,7 +144,7 @@ class Symbol extends graphic.Group { /** * Update symbol properties */ - updateData(data: List, idx: number, seriesScope?: SymbolDrawSeriesScope) { + updateData(data: List, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) { this.silent = false; const symbolType = data.getItemVisual(idx, 'symbol') || 'circle'; @@ -160,7 +165,7 @@ class Symbol extends graphic.Group { }, seriesModel, idx); } - this._updateCommon(data, idx, symbolSize, seriesScope); + this._updateCommon(data, idx, symbolSize, seriesScope, opts); if (isInit) { const symbolPath = this.childAt(0) as ECSymbol; @@ -187,7 +192,8 @@ class Symbol extends graphic.Group { data: List, idx: number, symbolSize: number[], - seriesScope?: SymbolDrawSeriesScope + seriesScope?: SymbolDrawSeriesScope, + opts?: SymbolOpts ) { const symbolPath = this.childAt(0) as ECSymbol; const seriesModel = data.hostModel as SeriesModel; @@ -263,7 +269,7 @@ class Symbol extends graphic.Group { else { symbolPath.useStyle(symbolStyle); } - symbolPath.setColor(visualColor, seriesScope && seriesScope.symbolInnerColor); + symbolPath.setColor(visualColor, opts && opts.symbolInnerColor); symbolPath.style.strokeNoScale = true; const liftZ = data.getItemVisual(idx, 'liftZ'); @@ -279,7 +285,7 @@ class Symbol extends graphic.Group { this._z2 = null; } - const useNameLabel = seriesScope && seriesScope.useNameLabel; + const useNameLabel = opts && opts.useNameLabel; setLabelStyle( symbolPath, labelStatesModels, diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts index bafa2c46e..84852fbed 100644 --- a/src/chart/helper/SymbolDraw.ts +++ b/src/chart/helper/SymbolDraw.ts @@ -115,10 +115,9 @@ export interface SymbolDrawSeriesScope { hoverAnimation?: boolean itemModel?: Model - symbolInnerColor?: ColorString + cursorStyle?: string fadeIn?: boolean - useNameLabel?: boolean } function makeSeriesScope(data: List): SymbolDrawSeriesScope { diff --git a/src/chart/sunburst/SunburstPiece.ts b/src/chart/sunburst/SunburstPiece.ts index 803585874..4edb92b4e 100644 --- a/src/chart/sunburst/SunburstPiece.ts +++ b/src/chart/sunburst/SunburstPiece.ts @@ -127,24 +127,11 @@ class SunburstPiece extends graphic.Sector { this._ecModel = ecModel || this._ecModel; const focus = emphasisModel.get('focus'); - let focusDataIndices: number[]; - - switch (focus) { - case 'ancestor': - focusDataIndices = []; - let currNode = node; - while (currNode) { - focusDataIndices.push(currNode.dataIndex); - currNode = currNode.parentNode; - } - break; - case 'descendant': - focusDataIndices = []; - node.eachNode(childNode => { - focusDataIndices.push(childNode.dataIndex); - }); - break; - } + + const focusDataIndices: number[] = focus === 'ancestor' + ? node.getAncestorsIndices() + : focus === 'descendant' ? node.getDescendantIndices() : null; + enableHoverEmphasis(this, focusDataIndices || focus, emphasisModel.get('blurScope')); } diff --git a/src/chart/tree/TreeView.ts b/src/chart/tree/TreeView.ts index dcc9266e2..ee16e765e 100644 --- a/src/chart/tree/TreeView.ts +++ b/src/chart/tree/TreeView.ts @@ -30,14 +30,14 @@ import { __DEV__ } from '../../config'; import {parsePercent} from '../../util/number'; import ChartView from '../../view/Chart'; import TreeSeriesModel, { TreeSeriesOption, TreeSeriesNodeItemOption } from './TreeSeries'; -import Path, { PathProps, PathStyleProps } from 'zrender/src/graphic/Path'; +import Path, { PathProps } from 'zrender/src/graphic/Path'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../ExtensionAPI'; -import Tree, { TreeNode } from '../../data/Tree'; +import { TreeNode } from '../../data/Tree'; import List from '../../data/List'; -import Model from '../../model/Model'; -import { ColorString, DisplayState, LabelOption } from '../../util/types'; import { getLabelStatesModels } from '../../label/labelStyle'; +import { setStatesStylesFromModel, setStatesFlag, setDefaultStateProxy } from '../../util/states'; +import { ECElement } from '../../util/types'; type TreeSymbol = SymbolClz & { __edge: graphic.BezierCurve | TreePath @@ -48,25 +48,6 @@ type TreeSymbol = SymbolClz & { __radialRawY: number }; -interface TreeSeriesScope extends Pick< - TreeSeriesOption, - 'expandAndCollapse' | 'edgeShape' | 'edgeForkPosition' - | 'layout' | 'orient' | 'symbolRotate' | 'symbolOffset' | 'hoverAnimation' -> { - curvature: number - useNameLabel: boolean - fadeIn: boolean - - itemModel: Model - itemStyle: PathStyleProps - hoverItemStyle: PathStyleProps - lineStyle: PathStyleProps - - labelStatesModels: Record> - - symbolInnerColor: ColorString -} - class TreeEdgeShape { parentPoint: number[] = []; childPoints: number[][] = []; @@ -146,7 +127,6 @@ class TreeView extends ChartView { static readonly type = 'tree'; readonly type = TreeView.type; - private _oldTree: Tree; private _mainGroup = new graphic.Group(); private _controller: RoamController; @@ -158,8 +138,6 @@ class TreeView extends ChartView { private _min: number[]; private _max: number[]; - private _viewCoordSys: View; - init(ecModel: GlobalModel, api: ExtensionAPI) { @@ -199,35 +177,21 @@ class TreeView extends ChartView { const oldData = this._data; - const seriesScope = { - expandAndCollapse: seriesModel.get('expandAndCollapse'), - layout: layout, - edgeShape: seriesModel.get('edgeShape'), - edgeForkPosition: seriesModel.get('edgeForkPosition'), - orient: seriesModel.getOrient(), - curvature: seriesModel.get(['lineStyle', 'curveness']), - symbolRotate: seriesModel.get('symbolRotate'), - symbolOffset: seriesModel.get('symbolOffset'), - hoverAnimation: seriesModel.get('hoverAnimation'), - useNameLabel: true, - fadeIn: true - } as TreeSeriesScope; - data.diff(oldData) .add(function (newIdx) { if (symbolNeedsDraw(data, newIdx)) { // Create node and edge - updateNode(data, newIdx, null, group, seriesModel, seriesScope); + updateNode(data, newIdx, null, group, seriesModel); } }) .update(function (newIdx, oldIdx) { const symbolEl = oldData.getItemGraphicEl(oldIdx) as TreeSymbol; if (!symbolNeedsDraw(data, newIdx)) { - symbolEl && removeNode(oldData, oldIdx, symbolEl, group, seriesModel, seriesScope); + symbolEl && removeNode(oldData, oldIdx, symbolEl, group, seriesModel); return; } // Update node and edge - updateNode(data, newIdx, symbolEl, group, seriesModel, seriesScope); + updateNode(data, newIdx, symbolEl, group, seriesModel); }) .remove(function (oldIdx) { const symbolEl = oldData.getItemGraphicEl(oldIdx) as TreeSymbol; @@ -237,7 +201,7 @@ class TreeView extends ChartView { // so if we want to remove the symbol element we should insure // that the symbol element is not null. if (symbolEl) { - removeNode(oldData, oldIdx, symbolEl, group, seriesModel, seriesScope); + removeNode(oldData, oldIdx, symbolEl, group, seriesModel); } }) .execute(); @@ -246,7 +210,7 @@ class TreeView extends ChartView { this._updateNodeAndLinkScale(seriesModel); - if (seriesScope.expandAndCollapse === true) { + if (seriesModel.get('expandAndCollapse') === true) { data.eachItemGraphicEl(function (el, dataIndex) { el.off('click').on('click', function () { api.dispatchAction({ @@ -304,7 +268,6 @@ class TreeView extends ChartView { scaleY: viewCoordSys.scaleY }); - this._viewCoordSys = viewCoordSys; this._min = min; this._max = max; } @@ -401,38 +364,21 @@ function symbolNeedsDraw(data: List, dataIndex: number) { && data.getItemVisual(dataIndex, 'symbol') !== 'none'; } -function getTreeNodeStyle( - node: TreeNode, - itemModel: Model, - seriesScope: TreeSeriesScope -): TreeSeriesScope { - seriesScope.itemModel = itemModel; - seriesScope.itemStyle = itemModel.getModel('itemStyle').getItemStyle(); - seriesScope.hoverItemStyle = itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle(); - seriesScope.lineStyle = itemModel.getModel('lineStyle').getLineStyle(); - seriesScope.labelStatesModels = getLabelStatesModels(itemModel); - - if (node.isExpand === false && node.children.length !== 0) { - seriesScope.symbolInnerColor = seriesScope.itemStyle.fill as ColorString; - } - else { - seriesScope.symbolInnerColor = '#fff'; - } - return seriesScope; -} function updateNode( data: List, dataIndex: number, symbolEl: TreeSymbol, group: graphic.Group, - seriesModel: TreeSeriesModel, - seriesScope: TreeSeriesScope + seriesModel: TreeSeriesModel ) { const isInit = !symbolEl; const node = data.tree.getNodeByDataIndex(dataIndex); - const itemModel = node.getModel(); - seriesScope = getTreeNodeStyle(node, itemModel, seriesScope); + const itemModel = node.getModel(); + const visualColor = node.getVisual('style').fill; + const symbolInnerColor = node.isExpand === false && node.children.length !== 0 + ? visualColor : '#fff'; + const virtualRoot = data.tree.root; const source = node.parentNode === virtualRoot ? node : node.parentNode || node; @@ -449,12 +395,18 @@ function updateNode( const targetLayout = node.getLayout(); if (isInit) { - symbolEl = new SymbolClz(data, dataIndex, seriesScope) as TreeSymbol; + symbolEl = new SymbolClz(data, dataIndex, null, { + symbolInnerColor, + useNameLabel: true + }) as TreeSymbol; symbolEl.x = sourceOldLayout.x; symbolEl.y = sourceOldLayout.y; } else { - symbolEl.updateData(data, dataIndex, seriesScope); + symbolEl.updateData(data, dataIndex, null, { + symbolInnerColor, + useNameLabel: true + }); } symbolEl.__radialOldRawX = symbolEl.__radialRawX; @@ -471,7 +423,7 @@ function updateNode( const symbolPath = symbolEl.getSymbolPath(); - if (seriesScope.layout === 'radial') { + if (seriesModel.get('layout') === 'radial') { const realRoot = virtualRoot.children[0]; const rootLayout = realRoot.getLayout(); const length = realRoot.children.length; @@ -512,7 +464,7 @@ function updateNode( } const textPosition = isLeft ? 'left' as const : 'right' as const; - const normalLabelModel = seriesScope.labelStatesModels.normal; + const normalLabelModel = itemModel.getModel('label'); const rotate = normalLabelModel.get('rotate'); const labelRotateRadian = rotate * (Math.PI / 180); @@ -528,11 +480,27 @@ function updateNode( } + // Handle status + const focus = itemModel.get(['emphasis', 'focus']); + const focusDataIndices: number[] = focus === 'ancestor' + ? node.getAncestorsIndices() + : focus === 'descendant' ? node.getDescendantIndices() : null; + + if (focusDataIndices) { + // Modify the focus to data indices. + graphic.getECData(symbolEl).focus = focusDataIndices; + } + drawEdge( seriesModel, node, virtualRoot, symbolEl, sourceOldLayout, - sourceLayout, targetLayout, group, seriesScope + sourceLayout, targetLayout, group ); + if (symbolEl.__edge) { + (symbolEl as ECElement).onStateChange = function (toState) { + setStatesFlag(symbolEl.__edge, toState); + }; + } } function drawEdge( @@ -543,29 +511,31 @@ function drawEdge( sourceOldLayout: TreeNodeLayout, sourceLayout: TreeNodeLayout, targetLayout: TreeNodeLayout, - group: graphic.Group, - seriesScope: TreeSeriesScope + group: graphic.Group ) { - - const edgeShape = seriesScope.edgeShape; + const itemModel = node.getModel(); + const edgeShape = seriesModel.get('edgeShape'); + const layout = seriesModel.get('layout'); + const orient = seriesModel.getOrient(); + const curvature = seriesModel.get(['lineStyle', 'curveness']); + const edgeForkPosition = seriesModel.get('edgeForkPosition'); + const lineStyle = itemModel.getModel('lineStyle').getLineStyle(); let edge = symbolEl.__edge; if (edgeShape === 'curve') { if (node.parentNode && node.parentNode !== virtualRoot) { if (!edge) { edge = symbolEl.__edge = new graphic.BezierCurve({ - shape: getEdgeShape(seriesScope, sourceOldLayout, sourceOldLayout), - style: zrUtil.defaults({opacity: 0, strokeNoScale: true}, seriesScope.lineStyle) + shape: getEdgeShape(layout, orient, curvature, sourceOldLayout, sourceOldLayout) }); } graphic.updateProps(edge as Path, { - shape: getEdgeShape(seriesScope, sourceLayout, targetLayout), - style: {opacity: 1} + shape: getEdgeShape(layout, orient, curvature, sourceLayout, targetLayout) }, seriesModel); } } else if (edgeShape === 'polyline') { - if (seriesScope.layout === 'orthogonal') { + if (layout === 'orthogonal') { if (node !== virtualRoot && node.children && (node.children.length !== 0) && (node.isExpand === true)) { const children = node.children; const childPoints = []; @@ -579,18 +549,16 @@ function drawEdge( shape: { parentPoint: [targetLayout.x, targetLayout.y], childPoints: [[targetLayout.x, targetLayout.y]], - orient: seriesScope.orient, - forkPosition: seriesScope.edgeForkPosition - }, - style: zrUtil.defaults({opacity: 0, strokeNoScale: true}, seriesScope.lineStyle) + orient: orient, + forkPosition: edgeForkPosition + } }); } graphic.updateProps(edge as Path, { shape: { parentPoint: [targetLayout.x, targetLayout.y], childPoints: childPoints - }, - style: {opacity: 1} + } }, seriesModel); } } @@ -600,7 +568,17 @@ function drawEdge( } } } - group.add(edge); + + if (edge) { + edge.useStyle(zrUtil.defaults({ + strokeNoScale: true, fill: null + }, lineStyle)); + + setStatesStylesFromModel(edge, itemModel, 'lineStyle'); + setDefaultStateProxy(edge); + + group.add(edge); + } } function removeNode( @@ -608,13 +586,10 @@ function removeNode( dataIndex: number, symbolEl: TreeSymbol, group: graphic.Group, - seriesModel: TreeSeriesModel, - seriesScope: TreeSeriesScope + seriesModel: TreeSeriesModel ) { const node = data.tree.getNodeByDataIndex(dataIndex); const virtualRoot = data.tree.root; - const itemModel = node.getModel(); - seriesScope = getTreeNodeStyle(node, itemModel, seriesScope); let source = node.parentNode === virtualRoot ? node : node.parentNode || node; // let edgeShape = seriesScope.edgeShape; @@ -643,12 +618,21 @@ function removeNode( const edge = symbolEl.__edge || ((source.isExpand === false || source.children.length === 1) ? sourceEdge : undefined); - const edgeShape = seriesScope.edgeShape; + const edgeShape = seriesModel.get('edgeShape'); + const layoutOpt = seriesModel.get('layout'); + const orient = seriesModel.get('orient'); + const curvature = seriesModel.get(['lineStyle', 'curveness']); if (edge) { if (edgeShape === 'curve') { graphic.updateProps(edge as Path, { - shape: getEdgeShape(seriesScope, sourceLayout, sourceLayout), + shape: getEdgeShape( + layoutOpt, + orient, + curvature, + sourceLayout, + sourceLayout + ), style: { opacity: 0 } @@ -656,7 +640,7 @@ function removeNode( group.remove(edge); }); } - else if (edgeShape === 'polyline' && seriesScope.layout === 'orthogonal') { + else if (edgeShape === 'polyline' && seriesModel.get('layout') === 'orthogonal') { graphic.updateProps(edge as Path, { shape: { parentPoint: [sourceLayout.x, sourceLayout.y], @@ -672,26 +656,31 @@ function removeNode( } } -function getEdgeShape(seriesScope: TreeSeriesScope, sourceLayout: TreeNodeLayout, targetLayout: TreeNodeLayout) { +function getEdgeShape( + layoutOpt: TreeSeriesOption['layout'], + orient: TreeSeriesOption['orient'], + curvature: number, + sourceLayout: TreeNodeLayout, + targetLayout: TreeNodeLayout +) { let cpx1: number; let cpy1: number; let cpx2: number; let cpy2: number; - const orient = seriesScope.orient; let x1: number; let x2: number; let y1: number; let y2: number; - if (seriesScope.layout === 'radial') { + if (layoutOpt === 'radial') { x1 = sourceLayout.rawX; y1 = sourceLayout.rawY; x2 = targetLayout.rawX; y2 = targetLayout.rawY; const radialCoor1 = radialCoordinate(x1, y1); - const radialCoor2 = radialCoordinate(x1, y1 + (y2 - y1) * seriesScope.curvature); - const radialCoor3 = radialCoordinate(x2, y2 + (y1 - y2) * seriesScope.curvature); + const radialCoor2 = radialCoordinate(x1, y1 + (y2 - y1) * curvature); + const radialCoor3 = radialCoordinate(x2, y2 + (y1 - y2) * curvature); const radialCoor4 = radialCoordinate(x2, y2); return { @@ -712,16 +701,16 @@ function getEdgeShape(seriesScope: TreeSeriesScope, sourceLayout: TreeNodeLayout y2 = targetLayout.y; if (orient === 'LR' || orient === 'RL') { - cpx1 = x1 + (x2 - x1) * seriesScope.curvature; + cpx1 = x1 + (x2 - x1) * curvature; cpy1 = y1; - cpx2 = x2 + (x1 - x2) * seriesScope.curvature; + cpx2 = x2 + (x1 - x2) * curvature; cpy2 = y2; } if (orient === 'TB' || orient === 'BT') { cpx1 = x1; - cpy1 = y1 + (y2 - y1) * seriesScope.curvature; + cpy1 = y1 + (y2 - y1) * curvature; cpx2 = x2; - cpy2 = y2 + (y1 - y2) * seriesScope.curvature; + cpy2 = y2 + (y1 - y2) * curvature; } } diff --git a/src/chart/treemap/TreemapView.ts b/src/chart/treemap/TreemapView.ts index e0060fcf0..495751a50 100644 --- a/src/chart/treemap/TreemapView.ts +++ b/src/chart/treemap/TreemapView.ts @@ -797,24 +797,9 @@ function renderNode( const focus = nodeModel.get(['emphasis', 'focus']); const blurScope = nodeModel.get(['emphasis', 'blurScope']); - let focusDataIndices: number[]; - switch (focus) { - case 'ancestor': - focusDataIndices = []; - let currNode = thisNode; - while (currNode) { - focusDataIndices.push(currNode.dataIndex); - currNode = currNode.parentNode; - } - break; - case 'descendant': - focusDataIndices = []; - thisNode.eachNode(childNode => { - focusDataIndices.push(childNode.dataIndex); - }); - break; - - } + const focusDataIndices: number[] = focus === 'ancestor' + ? thisNode.getAncestorsIndices() + : focus === 'descendant' ? thisNode.getDescendantIndices() : null; // No children, render content. if (isParent) { diff --git a/src/data/Tree.ts b/src/data/Tree.ts index ec3164c7e..154b62789 100644 --- a/src/data/Tree.ts +++ b/src/data/Tree.ts @@ -187,6 +187,24 @@ export class TreeNode { return ancestors; } + getAncestorsIndices(): number[] { + const indices: number[] = []; + let currNode = this as TreeNode; + while (currNode) { + indices.push(currNode.dataIndex); + currNode = currNode.parentNode; + } + return indices; + } + + getDescendantIndices(): number[] { + const indices: number[] = []; + this.eachNode(childNode => { + indices.push(childNode.dataIndex); + }); + return indices; + } + getValue(dimension?: DimensionLoose): ParsedValue { const data = this.hostTree.data; return data.get(data.getDimension(dimension || 'value'), this.dataIndex); diff --git a/test/hoverFocus.html b/test/hoverFocus.html index 3c340c407..6a837b8f4 100644 --- a/test/hoverFocus.html +++ b/test/hoverFocus.html @@ -72,10 +72,10 @@ under the License. graph: 'adjacency', sankey: 'adjacency', sunburst: 'descendant', - treemap: 'descendant' + treemap: 'descendant', + tree: 'ancestor' } allChartsOptions.forEach(function (chartOption) { - let isTreemap = false; chartOption.series.forEach(function (series) { series.emphasis = series.emphasis || {}; series.emphasis.focus = seriesFocusType[series.type] || 'series'; @@ -98,28 +98,26 @@ under the License. } if (series.type === 'treemap') { - isTreemap = true; series.itemStyle = { borderColor: 'rgba(100, 100, 200, 0.1)', borderWidth: 5 }; series.upperLabel = { show: true, - height: 30 + height: 15, + fontSize: 10 }; } }); - if (isTreemap) { - const dom = document.createElement('div'); - dom.className = 'chart'; - document.querySelector('#main').appendChild(dom); + const dom = document.createElement('div'); + dom.className = 'chart'; + document.querySelector('#main').appendChild(dom); - const chart = echarts.init(dom); + const chart = echarts.init(dom); - chart.setOption(chartOption); + chart.setOption(chartOption); - charts.push(chart); - } + charts.push(chart); }); function update() { -- GitLab