|  | /* | 
|  | * 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 SeriesModel from '../../model/Series'; | 
|  | import Tree from '../../data/Tree'; | 
|  | import { | 
|  | SeriesOption, | 
|  | SymbolOptionMixin, | 
|  | BoxLayoutOptionMixin, | 
|  | RoamOptionMixin, | 
|  | LineStyleOption, | 
|  | ItemStyleOption, | 
|  | SeriesLabelOption, | 
|  | OptionDataValue, | 
|  | StatesOptionMixin, | 
|  | OptionDataItemObject, | 
|  | CallbackDataParams, | 
|  | DefaultEmphasisFocus | 
|  | } from '../../util/types'; | 
|  | import SeriesData from '../../data/SeriesData'; | 
|  | import View from '../../coord/View'; | 
|  | import { LayoutRect } from '../../util/layout'; | 
|  | import Model from '../../model/Model'; | 
|  | import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; | 
|  | import { wrapTreePathInfo } from '../helper/treeHelper'; | 
|  |  | 
|  | interface CurveLineStyleOption extends LineStyleOption{ | 
|  | curveness?: number | 
|  | } | 
|  |  | 
|  | export interface TreeSeriesStateOption<TCbParams = never> { | 
|  | itemStyle?: ItemStyleOption<TCbParams> | 
|  | /** | 
|  | * Line style of the edge between node and it's parent. | 
|  | */ | 
|  | lineStyle?: CurveLineStyleOption | 
|  | label?: SeriesLabelOption | 
|  | } | 
|  |  | 
|  | interface TreeStatesMixin { | 
|  | emphasis?: { | 
|  | focus?: DefaultEmphasisFocus | 'ancestor' | 'descendant' | 'relative' | 
|  | scale?: boolean | 
|  | } | 
|  | } | 
|  |  | 
|  | export interface TreeSeriesNodeItemOption extends SymbolOptionMixin<CallbackDataParams>, | 
|  | TreeSeriesStateOption<CallbackDataParams>, | 
|  | StatesOptionMixin<TreeSeriesStateOption<CallbackDataParams>, TreeStatesMixin>, | 
|  | OptionDataItemObject<OptionDataValue> { | 
|  |  | 
|  | children?: TreeSeriesNodeItemOption[] | 
|  |  | 
|  | collapsed?: boolean | 
|  |  | 
|  | link?: string | 
|  | target?: string | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Configuration of leaves nodes. | 
|  | */ | 
|  | export interface TreeSeriesLeavesOption | 
|  | extends TreeSeriesStateOption, StatesOptionMixin<TreeSeriesStateOption, TreeStatesMixin> { | 
|  | } | 
|  |  | 
|  | export interface TreeSeriesOption extends | 
|  | SeriesOption<TreeSeriesStateOption, TreeStatesMixin>, TreeSeriesStateOption, | 
|  | SymbolOptionMixin, BoxLayoutOptionMixin, RoamOptionMixin { | 
|  | type?: 'tree' | 
|  |  | 
|  | layout?: 'orthogonal' | 'radial' | 
|  |  | 
|  | edgeShape?: 'polyline' | 'curve' | 
|  |  | 
|  | /** | 
|  | * Available when edgeShape is polyline | 
|  | */ | 
|  | edgeForkPosition?: string | number | 
|  |  | 
|  | nodeScaleRatio?: number | 
|  |  | 
|  | /** | 
|  | * The orient of orthoginal layout, can be setted to 'LR', 'TB', 'RL', 'BT'. | 
|  | * and the backward compatibility configuration 'horizontal = LR', 'vertical = TB'. | 
|  | */ | 
|  | orient?: 'LR' | 'TB' | 'RL' | 'BT' | 'horizontal' | 'vertical' | 
|  |  | 
|  | expandAndCollapse?: boolean | 
|  |  | 
|  | /** | 
|  | * The initial expanded depth of tree | 
|  | */ | 
|  | initialTreeDepth?: number | 
|  |  | 
|  | leaves?: TreeSeriesLeavesOption | 
|  |  | 
|  | data?: TreeSeriesNodeItemOption[] | 
|  | } | 
|  |  | 
|  | export interface TreeAncestors { | 
|  | name: string | 
|  | dataIndex: number | 
|  | value: number | 
|  | } | 
|  |  | 
|  | export interface TreeSeriesCallbackDataParams extends CallbackDataParams { | 
|  | collapsed: boolean; | 
|  | treeAncestors?: TreeAncestors[] | 
|  | } | 
|  |  | 
|  | class TreeSeriesModel extends SeriesModel<TreeSeriesOption> { | 
|  | static readonly type = 'series.tree'; | 
|  |  | 
|  | // can support the position parameters 'left', 'top','right','bottom', 'width', | 
|  | // 'height' in the setOption() with 'merge' mode normal. | 
|  | static readonly layoutMode = 'box'; | 
|  |  | 
|  | coordinateSystem: View; | 
|  |  | 
|  | layoutInfo: LayoutRect; | 
|  |  | 
|  | hasSymbolVisual = true; | 
|  |  | 
|  | // Do it self. | 
|  | ignoreStyleOnData = true; | 
|  |  | 
|  | /** | 
|  | * Init a tree data structure from data in option series | 
|  | */ | 
|  | getInitialData(option: TreeSeriesOption): SeriesData { | 
|  |  | 
|  | // create a virtual root | 
|  | const root: TreeSeriesNodeItemOption = { | 
|  | name: option.name, | 
|  | children: option.data | 
|  | }; | 
|  |  | 
|  | const leaves = option.leaves || {}; | 
|  | const leavesModel = new Model(leaves, this, this.ecModel); | 
|  |  | 
|  | const tree = Tree.createTree(root, this, beforeLink); | 
|  |  | 
|  | function beforeLink(nodeData: SeriesData) { | 
|  | nodeData.wrapMethod('getItemModel', function (model, idx) { | 
|  | const node = tree.getNodeByDataIndex(idx); | 
|  | if (!(node && node.children.length && node.isExpand)) { | 
|  | model.parentModel = leavesModel; | 
|  | } | 
|  | return model; | 
|  | }); | 
|  | } | 
|  |  | 
|  | let treeDepth = 0; | 
|  |  | 
|  | tree.eachNode('preorder', function (node) { | 
|  | if (node.depth > treeDepth) { | 
|  | treeDepth = node.depth; | 
|  | } | 
|  | }); | 
|  |  | 
|  | const expandAndCollapse = option.expandAndCollapse; | 
|  | const expandTreeDepth = (expandAndCollapse && option.initialTreeDepth >= 0) | 
|  | ? option.initialTreeDepth : treeDepth; | 
|  |  | 
|  | tree.root.eachNode('preorder', function (node) { | 
|  | const item = node.hostTree.data.getRawDataItem(node.dataIndex) as TreeSeriesNodeItemOption; | 
|  | // Add item.collapsed != null, because users can collapse node original in the series.data. | 
|  | node.isExpand = (item && item.collapsed != null) | 
|  | ? !item.collapsed | 
|  | : node.depth <= expandTreeDepth; | 
|  | }); | 
|  |  | 
|  | return tree.data; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Make the configuration 'orient' backward compatibly, with 'horizontal = LR', 'vertical = TB'. | 
|  | * @returns {string} orient | 
|  | */ | 
|  | getOrient() { | 
|  | let orient = this.get('orient'); | 
|  | if (orient === 'horizontal') { | 
|  | orient = 'LR'; | 
|  | } | 
|  | else if (orient === 'vertical') { | 
|  | orient = 'TB'; | 
|  | } | 
|  | return orient; | 
|  | } | 
|  |  | 
|  | setZoom(zoom: number) { | 
|  | this.option.zoom = zoom; | 
|  | } | 
|  |  | 
|  | setCenter(center: number[]) { | 
|  | this.option.center = center; | 
|  | } | 
|  |  | 
|  | formatTooltip( | 
|  | dataIndex: number, | 
|  | multipleSeries: boolean, | 
|  | dataType: string | 
|  | ) { | 
|  | const tree = this.getData().tree; | 
|  | const realRoot = tree.root.children[0]; | 
|  | let node = tree.getNodeByDataIndex(dataIndex); | 
|  | const value = node.getValue(); | 
|  | let name = node.name; | 
|  | while (node && (node !== realRoot)) { | 
|  | name = node.parentNode.name + '.' + name; | 
|  | node = node.parentNode; | 
|  | } | 
|  |  | 
|  | return createTooltipMarkup('nameValue', { | 
|  | name: name, | 
|  | value: value, | 
|  | noValue: isNaN(value as number) || value == null | 
|  | }); | 
|  | } | 
|  |  | 
|  | // Add tree path to tooltip param | 
|  | getDataParams(dataIndex: number) { | 
|  | const params = super.getDataParams.apply(this, arguments as any) as TreeSeriesCallbackDataParams; | 
|  |  | 
|  | const node = this.getData().tree.getNodeByDataIndex(dataIndex); | 
|  | params.treeAncestors = wrapTreePathInfo(node, this); | 
|  | params.collapsed = !node.isExpand; | 
|  |  | 
|  | return params; | 
|  | } | 
|  |  | 
|  | static defaultOption: TreeSeriesOption = { | 
|  | // zlevel: 0, | 
|  | z: 2, | 
|  | coordinateSystem: 'view', | 
|  |  | 
|  | // the position of the whole view | 
|  | left: '12%', | 
|  | top: '12%', | 
|  | right: '12%', | 
|  | bottom: '12%', | 
|  |  | 
|  | // the layout of the tree, two value can be selected, 'orthogonal' or 'radial' | 
|  | layout: 'orthogonal', | 
|  |  | 
|  | // value can be 'polyline' | 
|  | edgeShape: 'curve', | 
|  |  | 
|  | edgeForkPosition: '50%', | 
|  |  | 
|  | // true | false | 'move' | 'scale', see module:component/helper/RoamController. | 
|  | roam: false, | 
|  |  | 
|  | // Symbol size scale ratio in roam | 
|  | nodeScaleRatio: 0.4, | 
|  |  | 
|  | // Default on center of graph | 
|  | center: null, | 
|  |  | 
|  | zoom: 1, | 
|  |  | 
|  | orient: 'LR', | 
|  |  | 
|  | symbol: 'emptyCircle', | 
|  |  | 
|  | symbolSize: 7, | 
|  |  | 
|  | expandAndCollapse: true, | 
|  |  | 
|  | initialTreeDepth: 2, | 
|  |  | 
|  | lineStyle: { | 
|  | color: '#ccc', | 
|  | width: 1.5, | 
|  | curveness: 0.5 | 
|  | }, | 
|  |  | 
|  | itemStyle: { | 
|  | color: 'lightsteelblue', | 
|  | // borderColor: '#c23531', | 
|  | borderWidth: 1.5 | 
|  | }, | 
|  |  | 
|  | label: { | 
|  | show: true | 
|  | }, | 
|  |  | 
|  | animationEasing: 'linear', | 
|  |  | 
|  | animationDuration: 700, | 
|  |  | 
|  | animationDurationUpdate: 500 | 
|  | }; | 
|  | } | 
|  |  | 
|  | export default TreeSeriesModel; |