| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import * as zrUtil from 'zrender/src/core/util'; |
| import ChartView from '../../view/Chart'; |
| import SunburstPiece from './SunburstPiece'; |
| import DataDiffer from '../../data/DataDiffer'; |
| import SunburstSeriesModel, { SunburstSeriesNodeItemOption } from './SunburstSeries'; |
| import GlobalModel from '../../model/Global'; |
| import ExtensionAPI from '../../core/ExtensionAPI'; |
| import { TreeNode } from '../../data/Tree'; |
| import { ROOT_TO_NODE_ACTION } from './sunburstAction'; |
| import { windowOpen } from '../../util/format'; |
| |
| interface DrawTreeNode extends TreeNode { |
| parentNode: DrawTreeNode |
| piece: SunburstPiece |
| children: DrawTreeNode[] |
| } |
| class SunburstView extends ChartView { |
| |
| static readonly type = 'sunburst'; |
| readonly type = SunburstView.type; |
| |
| seriesModel: SunburstSeriesModel; |
| api: ExtensionAPI; |
| ecModel: GlobalModel; |
| |
| virtualPiece: SunburstPiece; |
| |
| private _oldChildren: DrawTreeNode[]; |
| |
| render( |
| seriesModel: SunburstSeriesModel, |
| ecModel: GlobalModel, |
| api: ExtensionAPI, |
| // @ts-ignore |
| payload |
| ) { |
| const self = this; |
| |
| this.seriesModel = seriesModel; |
| this.api = api; |
| this.ecModel = ecModel; |
| |
| const data = seriesModel.getData(); |
| const virtualRoot = data.tree.root as DrawTreeNode; |
| |
| const newRoot = seriesModel.getViewRoot() as DrawTreeNode; |
| |
| const group = this.group; |
| |
| const renderLabelForZeroData = seriesModel.get('renderLabelForZeroData'); |
| |
| const newChildren: DrawTreeNode[] = []; |
| newRoot.eachNode(function (node: DrawTreeNode) { |
| newChildren.push(node); |
| }); |
| const oldChildren = this._oldChildren || []; |
| |
| dualTravel(newChildren, oldChildren); |
| |
| renderRollUp(virtualRoot, newRoot); |
| |
| this._initEvents(); |
| |
| this._oldChildren = newChildren; |
| |
| function dualTravel(newChildren: DrawTreeNode[], oldChildren: DrawTreeNode[]) { |
| if (newChildren.length === 0 && oldChildren.length === 0) { |
| return; |
| } |
| |
| new DataDiffer(oldChildren, newChildren, getKey, getKey) |
| .add(processNode) |
| .update(processNode) |
| .remove(zrUtil.curry(processNode, null)) |
| .execute(); |
| |
| function getKey(node: DrawTreeNode) { |
| return node.getId(); |
| } |
| |
| function processNode(newIdx: number, oldIdx?: number) { |
| const newNode = newIdx == null ? null : newChildren[newIdx]; |
| const oldNode = oldIdx == null ? null : oldChildren[oldIdx]; |
| |
| doRenderNode(newNode, oldNode); |
| } |
| } |
| |
| function doRenderNode(newNode: DrawTreeNode, oldNode: DrawTreeNode) { |
| if (!renderLabelForZeroData && newNode && !newNode.getValue()) { |
| // Not render data with value 0 |
| newNode = null; |
| } |
| |
| if (newNode !== virtualRoot && oldNode !== virtualRoot) { |
| if (oldNode && oldNode.piece) { |
| if (newNode) { |
| // Update |
| oldNode.piece.updateData( |
| false, newNode, seriesModel, ecModel, api |
| ); |
| |
| // For tooltip |
| data.setItemGraphicEl(newNode.dataIndex, oldNode.piece); |
| } |
| else { |
| // Remove |
| removeNode(oldNode); |
| } |
| } |
| else if (newNode) { |
| // Add |
| const piece = new SunburstPiece( |
| newNode, |
| seriesModel, |
| ecModel, |
| api |
| ); |
| group.add(piece); |
| |
| // For tooltip |
| data.setItemGraphicEl(newNode.dataIndex, piece); |
| } |
| } |
| } |
| |
| function removeNode(node: DrawTreeNode) { |
| if (!node) { |
| return; |
| } |
| |
| if (node.piece) { |
| group.remove(node.piece); |
| node.piece = null; |
| } |
| } |
| |
| function renderRollUp(virtualRoot: DrawTreeNode, viewRoot: DrawTreeNode) { |
| if (viewRoot.depth > 0) { |
| // Render |
| if (self.virtualPiece) { |
| // Update |
| self.virtualPiece.updateData( |
| false, virtualRoot, seriesModel, ecModel, api |
| ); |
| } |
| else { |
| // Add |
| self.virtualPiece = new SunburstPiece( |
| virtualRoot, |
| seriesModel, |
| ecModel, |
| api |
| ); |
| group.add(self.virtualPiece); |
| } |
| |
| // TODO event scope |
| viewRoot.piece.off('click'); |
| self.virtualPiece.on('click', function (e) { |
| self._rootToNode(viewRoot.parentNode); |
| }); |
| } |
| else if (self.virtualPiece) { |
| // Remove |
| group.remove(self.virtualPiece); |
| self.virtualPiece = null; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| _initEvents() { |
| this.group.off('click'); |
| this.group.on('click', (e) => { |
| let targetFound = false; |
| const viewRoot = this.seriesModel.getViewRoot(); |
| viewRoot.eachNode((node: DrawTreeNode) => { |
| if (!targetFound |
| && node.piece && node.piece === e.target |
| ) { |
| const nodeClick = node.getModel<SunburstSeriesNodeItemOption>().get('nodeClick'); |
| if (nodeClick === 'rootToNode') { |
| this._rootToNode(node); |
| } |
| else if (nodeClick === 'link') { |
| const itemModel = node.getModel<SunburstSeriesNodeItemOption>(); |
| const link = itemModel.get('link'); |
| if (link) { |
| const linkTarget = itemModel.get('target', true) |
| || '_blank'; |
| windowOpen(link, linkTarget); |
| } |
| } |
| targetFound = true; |
| } |
| }); |
| }); |
| } |
| |
| /** |
| * @private |
| */ |
| _rootToNode(node: DrawTreeNode) { |
| if (node !== this.seriesModel.getViewRoot()) { |
| this.api.dispatchAction({ |
| type: ROOT_TO_NODE_ACTION, |
| from: this.uid, |
| seriesId: this.seriesModel.id, |
| targetNode: node |
| }); |
| } |
| } |
| |
| /** |
| * @implement |
| */ |
| containPoint(point: number[], seriesModel: SunburstSeriesModel) { |
| const treeRoot = seriesModel.getData(); |
| const itemLayout = treeRoot.getItemLayout(0); |
| if (itemLayout) { |
| const dx = point[0] - itemLayout.cx; |
| const dy = point[1] - itemLayout.cy; |
| const radius = Math.sqrt(dx * dx + dy * dy); |
| return radius <= itemLayout.r && radius >= itemLayout.r0; |
| } |
| } |
| |
| } |
| |
| export default SunburstView; |