| /* | |
| * 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, | |
| BlurScope | |
| } from '../../util/types'; | |
| import GlobalModel from '../../model/Global'; | |
| import { LayoutRect } from '../../util/layout'; | |
| import SeriesData from '../../data/SeriesData'; | |
| import { normalizeToArray } from '../../util/model'; | |
| import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; | |
| import enableAriaDecalForTree from '../helper/enableAriaDecalForTree'; | |
| // Only support numeric value. | |
| type TreemapSeriesDataValue = number | number[]; | |
| interface BreadcrumbItemStyleOption extends ItemStyleOption { | |
| // TODO: textStyle should be in breadcrumb.label | |
| textStyle?: LabelOption | |
| } | |
| interface TreemapSeriesLabelOption extends SeriesLabelOption { | |
| formatter?: string | ((params: CallbackDataParams) => string) | |
| } | |
| interface TreemapSeriesItemStyleOption<TCbParams = never> extends ItemStyleOption<TCbParams> { | |
| 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<TCbParams = never> { | |
| itemStyle?: TreemapSeriesItemStyleOption<TCbParams> | |
| 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 support 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<TreemapSeriesCallbackDataParams>, ExtraStateOption>, | |
| TreemapStateOption<TreemapSeriesCallbackDataParams>, | |
| 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 behaviour defined above. | |
| */ | |
| nodeClick?: 'zoomToNode' | 'link' | false | |
| breadcrumb?: BoxLayoutOptionMixin & { | |
| show?: boolean | |
| height?: number | |
| emptyItemWidth?: number // With of empty width | |
| itemStyle?: BreadcrumbItemStyleOption | |
| emphasis?: { | |
| disabled?: boolean | |
| focus?: DefaultEmphasisFocus | |
| blurScope?: BlurScope | |
| 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' | |
| } | |
| }, | |
| emphasis: { | |
| itemStyle: { | |
| color: 'rgba(0,0,0,0.9)' // '#5793f3', | |
| } | |
| } | |
| }, | |
| 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 string 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%'], | |
| overflow: 'truncate', | |
| 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 | |
| // } | |
| }; | |
| /** | |
| * @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: SeriesData) { | |
| 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; |