| /* |
| * 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 SeriesModel from '../../model/Series'; |
| import Tree, { TreeNode } from '../../data/Tree'; |
| import Model from '../../model/Model'; |
| import {wrapTreePathInfo} from '../helper/treeHelper'; |
| import { |
| SeriesOption, |
| BoxLayoutOptionMixin, |
| ItemStyleOption, |
| LabelOption, |
| RoamOptionMixin, |
| CallbackDataParams, |
| ColorString, |
| StatesOptionMixin, |
| OptionId, |
| OptionName, |
| DecalObject, |
| SeriesLabelOption, |
| DefaultEmphasisFocus, |
| AriaOptionMixin, |
| ColorBy |
| } from '../../util/types'; |
| import GlobalModel from '../../model/Global'; |
| import { LayoutRect } from '../../util/layout'; |
| import List from '../../data/List'; |
| import { normalizeToArray } from '../../util/model'; |
| import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; |
| import enableAriaDecalForTree from '../helper/enableAriaDecalForTree'; |
| |
| // Only support numberic value. |
| type TreemapSeriesDataValue = number | number[]; |
| |
| interface BreadcrumbItemStyleOption extends ItemStyleOption { |
| // TODO: textStyle should be in breadcrumb.label |
| textStyle?: LabelOption |
| } |
| |
| interface TreemapSeriesLabelOption extends SeriesLabelOption { |
| ellipsis?: boolean |
| formatter?: string | ((params: CallbackDataParams) => string) |
| } |
| |
| interface TreemapSeriesItemStyleOption extends ItemStyleOption { |
| borderRadius?: number | number[] |
| |
| colorAlpha?: number |
| colorSaturation?: number |
| |
| borderColorSaturation?: number |
| |
| gapWidth?: number |
| } |
| |
| interface TreePathInfo { |
| name: string |
| dataIndex: number |
| value: TreemapSeriesDataValue |
| } |
| |
| interface TreemapSeriesCallbackDataParams extends CallbackDataParams { |
| /** |
| * @deprecated |
| */ |
| treePathInfo?: TreePathInfo[] |
| |
| treeAncestors?: TreePathInfo[] |
| } |
| |
| interface ExtraStateOption { |
| emphasis?: { |
| focus?: DefaultEmphasisFocus | 'descendant' | 'ancestor' |
| } |
| } |
| |
| export interface TreemapStateOption { |
| itemStyle?: TreemapSeriesItemStyleOption |
| label?: TreemapSeriesLabelOption |
| upperLabel?: TreemapSeriesLabelOption |
| } |
| |
| export interface TreemapSeriesVisualOption { |
| /** |
| * Which dimension will be applied with the visual properties. |
| */ |
| visualDimension?: number | string |
| |
| /** |
| * @deprecated Use colorBy instead |
| */ |
| colorMappingBy?: 'value' | 'index' | 'id' |
| |
| visualMin?: number |
| visualMax?: number |
| |
| colorAlpha?: number[] | 'none' |
| colorSaturation?: number[] | 'none' |
| // A color list for a level. Each node in the level will obtain a color from the color list. |
| // Only suuport ColorString for interpolation |
| // color?: ColorString[] |
| |
| /** |
| * A node will not be shown when its area size is smaller than this value (unit: px square). |
| */ |
| visibleMin?: number |
| /** |
| * Children will not be shown when area size of a node is smaller than this value (unit: px square). |
| */ |
| childrenVisibleMin?: number |
| } |
| |
| export interface TreemapSeriesLevelOption extends TreemapSeriesVisualOption, |
| TreemapStateOption, StatesOptionMixin<TreemapStateOption, ExtraStateOption> { |
| |
| color?: ColorString[] | 'none', |
| decal?: DecalObject[] | 'none' |
| } |
| |
| export interface TreemapSeriesNodeItemOption extends TreemapSeriesVisualOption, |
| TreemapStateOption, StatesOptionMixin<TreemapStateOption, ExtraStateOption> { |
| id?: OptionId |
| name?: OptionName |
| |
| value?: TreemapSeriesDataValue |
| |
| children?: TreemapSeriesNodeItemOption[] |
| |
| color?: ColorString[] | 'none' |
| |
| decal?: DecalObject[] | 'none' |
| } |
| |
| export interface TreemapSeriesOption |
| extends SeriesOption<TreemapStateOption, ExtraStateOption>, |
| TreemapStateOption, |
| BoxLayoutOptionMixin, |
| RoamOptionMixin, |
| TreemapSeriesVisualOption { |
| |
| type?: 'treemap' |
| |
| /** |
| * configuration in echarts2 |
| * @deprecated |
| */ |
| size?: (number | string)[] |
| |
| /** |
| * If sort in desc order. |
| * Default to be desc. asc has strange effect |
| */ |
| sort?: boolean | 'asc' | 'desc' |
| |
| /** |
| * Size of clipped window when zooming. 'origin' or 'fullscreen' |
| */ |
| clipWindow?: 'origin' | 'fullscreen' |
| |
| squareRatio?: number |
| /** |
| * Nodes on depth from root are regarded as leaves. |
| * Count from zero (zero represents only view root). |
| */ |
| leafDepth?: number |
| |
| drillDownIcon?: string |
| |
| /** |
| * Be effective when using zoomToNode. Specify the proportion of the |
| * target node area in the view area. |
| */ |
| zoomToNodeRatio?: number |
| /** |
| * Leaf node click behaviour: 'zoomToNode', 'link', false. |
| * If leafDepth is set and clicking a node which has children but |
| * be on left depth, the behaviour would be changing root. Otherwise |
| * use behavious defined above. |
| */ |
| nodeClick?: 'zoomToNode' | 'link' |
| |
| breadcrumb?: BoxLayoutOptionMixin & { |
| show?: boolean |
| height?: number |
| |
| emptyItemWidth?: number // With of empty width |
| itemStyle?: BreadcrumbItemStyleOption |
| |
| emphasis?: { |
| itemStyle?: BreadcrumbItemStyleOption |
| } |
| } |
| |
| levels?: TreemapSeriesLevelOption[] |
| |
| data?: TreemapSeriesNodeItemOption[] |
| } |
| |
| class TreemapSeriesModel extends SeriesModel<TreemapSeriesOption> { |
| |
| static type = 'series.treemap'; |
| type = TreemapSeriesModel.type; |
| |
| static layoutMode = 'box' as const; |
| |
| preventUsingHoverLayer = true; |
| |
| layoutInfo: LayoutRect; |
| |
| designatedVisualItemStyle: TreemapSeriesItemStyleOption; |
| |
| private _viewRoot: TreeNode; |
| private _idIndexMap: zrUtil.HashMap<number>; |
| private _idIndexMapCount: number; |
| |
| static defaultOption: TreemapSeriesOption = { |
| // Disable progressive rendering |
| progressive: 0, |
| // size: ['80%', '80%'], // deprecated, compatible with ec2. |
| left: 'center', |
| top: 'middle', |
| width: '80%', |
| height: '80%', |
| sort: true, |
| |
| clipWindow: 'origin', |
| squareRatio: 0.5 * (1 + Math.sqrt(5)), // golden ratio |
| leafDepth: null, |
| |
| drillDownIcon: '▶', // Use html character temporarily because it is complicated |
| // to align specialized icon. ▷▶❒❐▼✚ |
| |
| zoomToNodeRatio: 0.32 * 0.32, |
| |
| roam: true, |
| nodeClick: 'zoomToNode', |
| animation: true, |
| animationDurationUpdate: 900, |
| animationEasing: 'quinticInOut', |
| breadcrumb: { |
| show: true, |
| height: 22, |
| left: 'center', |
| top: 'bottom', |
| // right |
| // bottom |
| emptyItemWidth: 25, // Width of empty node. |
| itemStyle: { |
| color: 'rgba(0,0,0,0.7)', //'#5793f3', |
| textStyle: { |
| color: '#fff' |
| } |
| } |
| }, |
| label: { |
| show: true, |
| // Do not use textDistance, for ellipsis rect just the same as treemap node rect. |
| distance: 0, |
| padding: 5, |
| position: 'inside', // Can be [5, '5%'] or position stirng like 'insideTopLeft', ... |
| // formatter: null, |
| color: '#fff', |
| overflow: 'truncate' |
| // align |
| // verticalAlign |
| }, |
| upperLabel: { // Label when node is parent. |
| show: false, |
| position: [0, '50%'], |
| height: 20, |
| // formatter: null, |
| // color: '#fff', |
| overflow: 'truncate', |
| // align: null, |
| verticalAlign: 'middle' |
| }, |
| itemStyle: { |
| color: null, // Can be 'none' if not necessary. |
| colorAlpha: null, // Can be 'none' if not necessary. |
| colorSaturation: null, // Can be 'none' if not necessary. |
| borderWidth: 0, |
| gapWidth: 0, |
| borderColor: '#fff', |
| borderColorSaturation: null // If specified, borderColor will be ineffective, and the |
| // border color is evaluated by color of current node and |
| // borderColorSaturation. |
| }, |
| emphasis: { |
| upperLabel: { |
| show: true, |
| position: [0, '50%'], |
| ellipsis: true, |
| verticalAlign: 'middle' |
| } |
| }, |
| |
| visualDimension: 0, // Can be 0, 1, 2, 3. |
| visualMin: null, |
| visualMax: null, |
| |
| color: [], // + treemapSeries.color should not be modified. Please only modified |
| // level[n].color (if necessary). |
| // + Specify color list of each level. level[0].color would be global |
| // color list if not specified. (see method `setDefault`). |
| // + But set as a empty array to forbid fetch color from global palette |
| // when using nodeModel.get('color'), otherwise nodes on deep level |
| // will always has color palette set and are not able to inherit color |
| // from parent node. |
| // + TreemapSeries.color can not be set as 'none', otherwise effect |
| // legend color fetching (see seriesColor.js). |
| colorAlpha: null, // Array. Specify color alpha range of each level, like [0.2, 0.8] |
| colorSaturation: null, // Array. Specify color saturation of each level, like [0.2, 0.5] |
| colorMappingBy: 'index', // 'value' or 'index' or 'id'. |
| visibleMin: 10, // If area less than this threshold (unit: pixel^2), node will not |
| // be rendered. Only works when sort is 'asc' or 'desc'. |
| childrenVisibleMin: null, // If area of a node less than this threshold (unit: pixel^2), |
| // grandchildren will not show. |
| // Why grandchildren? If not grandchildren but children, |
| // some siblings show children and some not, |
| // the appearance may be mess and not consistent, |
| levels: [] // Each item: { |
| // visibleMin, itemStyle, visualDimension, label |
| // } |
| // data: { |
| // value: [], |
| // children: [], |
| // link: 'http://xxx.xxx.xxx', |
| // target: 'blank' or 'self' |
| // } |
| }; |
| |
| /** |
| * @override |
| */ |
| getInitialData(option: TreemapSeriesOption, ecModel: GlobalModel) { |
| // Create a virtual root. |
| const root: TreemapSeriesNodeItemOption = { |
| name: option.name, |
| children: option.data |
| }; |
| |
| completeTreeValue(root); |
| |
| let levels = option.levels || []; |
| |
| // Used in "visual priority" in `treemapVisual.js`. |
| // This way is a little tricky, must satisfy the precondition: |
| // 1. There is no `treeNode.getModel('itemStyle.xxx')` used. |
| // 2. The `Model.prototype.getModel()` will not use any clone-like way. |
| const designatedVisualItemStyle = this.designatedVisualItemStyle = {}; |
| const designatedVisualModel = new Model({itemStyle: designatedVisualItemStyle}, this, ecModel); |
| |
| levels = option.levels = setDefault(levels, ecModel); |
| const levelModels = zrUtil.map(levels || [], function (levelDefine) { |
| return new Model(levelDefine, designatedVisualModel, ecModel); |
| }, this); |
| |
| // Make sure always a new tree is created when setOption, |
| // in TreemapView, we check whether oldTree === newTree |
| // to choose mappings approach among old shapes and new shapes. |
| const tree = Tree.createTree(root, this, beforeLink); |
| |
| function beforeLink(nodeData: List) { |
| nodeData.wrapMethod('getItemModel', function (model, idx) { |
| const node = tree.getNodeByDataIndex(idx); |
| const levelModel = node ? levelModels[node.depth] : null; |
| // If no levelModel, we also need `designatedVisualModel`. |
| model.parentModel = levelModel || designatedVisualModel; |
| return model; |
| }); |
| } |
| |
| return tree.data; |
| } |
| |
| optionUpdated() { |
| this.resetViewRoot(); |
| } |
| |
| /** |
| * @override |
| * @param {number} dataIndex |
| * @param {boolean} [mutipleSeries=false] |
| */ |
| formatTooltip( |
| dataIndex: number, |
| multipleSeries: boolean, |
| dataType: string |
| ) { |
| const data = this.getData(); |
| const value = this.getRawValue(dataIndex) as TreemapSeriesDataValue; |
| const name = data.getName(dataIndex); |
| |
| return createTooltipMarkup('nameValue', { name: name, value: value }); |
| } |
| |
| /** |
| * Add tree path to tooltip param |
| * |
| * @override |
| * @param {number} dataIndex |
| * @return {Object} |
| */ |
| getDataParams(dataIndex: number) { |
| const params = super.getDataParams.apply(this, arguments as any) as TreemapSeriesCallbackDataParams; |
| |
| const node = this.getData().tree.getNodeByDataIndex(dataIndex); |
| params.treeAncestors = wrapTreePathInfo(node, this); |
| // compatitable the previous code. |
| params.treePathInfo = params.treeAncestors; |
| |
| return params; |
| } |
| |
| /** |
| * @public |
| * @param {Object} layoutInfo { |
| * x: containerGroup x |
| * y: containerGroup y |
| * width: containerGroup width |
| * height: containerGroup height |
| * } |
| */ |
| setLayoutInfo(layoutInfo: LayoutRect) { |
| /** |
| * @readOnly |
| * @type {Object} |
| */ |
| this.layoutInfo = this.layoutInfo || {} as LayoutRect; |
| zrUtil.extend(this.layoutInfo, layoutInfo); |
| } |
| |
| /** |
| * @param {string} id |
| * @return {number} index |
| */ |
| mapIdToIndex(id: string): number { |
| // A feature is implemented: |
| // index is monotone increasing with the sequence of |
| // input id at the first time. |
| // This feature can make sure that each data item and its |
| // mapped color have the same index between data list and |
| // color list at the beginning, which is useful for user |
| // to adjust data-color mapping. |
| |
| /** |
| * @private |
| * @type {Object} |
| */ |
| let idIndexMap = this._idIndexMap; |
| |
| if (!idIndexMap) { |
| idIndexMap = this._idIndexMap = zrUtil.createHashMap(); |
| /** |
| * @private |
| * @type {number} |
| */ |
| this._idIndexMapCount = 0; |
| } |
| |
| let index = idIndexMap.get(id); |
| if (index == null) { |
| idIndexMap.set(id, index = this._idIndexMapCount++); |
| } |
| |
| return index; |
| } |
| |
| getViewRoot() { |
| return this._viewRoot; |
| } |
| |
| resetViewRoot(viewRoot?: TreeNode) { |
| viewRoot |
| ? (this._viewRoot = viewRoot) |
| : (viewRoot = this._viewRoot); |
| |
| const root = this.getRawData().tree.root; |
| |
| if (!viewRoot |
| || (viewRoot !== root && !root.contains(viewRoot)) |
| ) { |
| this._viewRoot = root; |
| } |
| } |
| |
| enableAriaDecal() { |
| enableAriaDecalForTree(this); |
| } |
| } |
| |
| /** |
| * @param {Object} dataNode |
| */ |
| function completeTreeValue(dataNode: TreemapSeriesNodeItemOption) { |
| // Postorder travel tree. |
| // If value of none-leaf node is not set, |
| // calculate it by suming up the value of all children. |
| let sum = 0; |
| |
| zrUtil.each(dataNode.children, function (child) { |
| |
| completeTreeValue(child); |
| |
| let childValue = child.value; |
| zrUtil.isArray(childValue) && (childValue = childValue[0]); |
| |
| sum += childValue; |
| }); |
| |
| let thisValue = dataNode.value; |
| if (zrUtil.isArray(thisValue)) { |
| thisValue = thisValue[0]; |
| } |
| |
| if (thisValue == null || isNaN(thisValue)) { |
| thisValue = sum; |
| } |
| // Value should not less than 0. |
| if (thisValue < 0) { |
| thisValue = 0; |
| } |
| |
| zrUtil.isArray(dataNode.value) |
| ? (dataNode.value[0] = thisValue) |
| : (dataNode.value = thisValue); |
| } |
| |
| /** |
| * set default to level configuration |
| */ |
| function setDefault(levels: TreemapSeriesLevelOption[], ecModel: GlobalModel) { |
| const globalColorList = normalizeToArray(ecModel.get('color')) as ColorString[]; |
| const globalDecalList = normalizeToArray( |
| (ecModel as Model<AriaOptionMixin>).get(['aria', 'decal', 'decals']) |
| ) as DecalObject[]; |
| |
| if (!globalColorList) { |
| return; |
| } |
| |
| levels = levels || []; |
| let hasColorDefine; |
| let hasDecalDefine; |
| zrUtil.each(levels, function (levelDefine) { |
| const model = new Model(levelDefine); |
| const modelColor = model.get('color'); |
| const modelDecal = model.get('decal'); |
| |
| if (model.get(['itemStyle', 'color']) |
| || (modelColor && modelColor !== 'none') |
| ) { |
| hasColorDefine = true; |
| } |
| if (model.get(['itemStyle', 'decal']) |
| || (modelDecal && modelDecal !== 'none') |
| ) { |
| hasDecalDefine = true; |
| } |
| }); |
| |
| const level0 = levels[0] || (levels[0] = {}); |
| if (!hasColorDefine) { |
| level0.color = globalColorList.slice(); |
| } |
| if (!hasDecalDefine && globalDecalList) { |
| level0.decal = globalDecalList.slice(); |
| } |
| |
| return levels; |
| } |
| |
| export default TreemapSeriesModel; |