| /* | |
| * 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 graphic from '../../util/graphic'; | |
| import {getECData} from '../../util/innerStore'; | |
| import * as layout from '../../util/layout'; | |
| import {wrapTreePathInfo} from '../helper/treeHelper'; | |
| import TreemapSeriesModel, { TreemapSeriesNodeItemOption, TreemapSeriesOption } from './TreemapSeries'; | |
| import ExtensionAPI from '../../core/ExtensionAPI'; | |
| import { TreeNode } from '../../data/Tree'; | |
| import { curry, defaults } from 'zrender/src/core/util'; | |
| import { ZRElementEvent, BoxLayoutOptionMixin, ECElement } from '../../util/types'; | |
| import Element from 'zrender/src/Element'; | |
| import Model from '../../model/Model'; | |
| import { convertOptionIdName } from '../../util/model'; | |
| import { toggleHoverEmphasis, Z2_EMPHASIS_LIFT } from '../../util/states'; | |
| import { createTextStyle } from '../../label/labelStyle'; | |
| const TEXT_PADDING = 8; | |
| const ITEM_GAP = 8; | |
| const ARRAY_LENGTH = 5; | |
| interface OnSelectCallback { | |
| (node: TreeNode, e: ZRElementEvent): void | |
| } | |
| interface LayoutParam { | |
| pos: BoxLayoutOptionMixin | |
| box: { | |
| width: number, | |
| height: number | |
| } | |
| emptyItemWidth: number | |
| totalWidth: number | |
| renderList: { | |
| node: TreeNode, | |
| text: string | |
| width: number | |
| }[] | |
| } | |
| type BreadcrumbItemStyleModel = Model<TreemapSeriesOption['breadcrumb']['itemStyle']>; | |
| type BreadcrumbEmphasisItemStyleModel = Model<TreemapSeriesOption['breadcrumb']['emphasis']>; | |
| type BreadcrumbTextStyleModel = Model<TreemapSeriesOption['breadcrumb']['itemStyle']['textStyle']>; | |
| class Breadcrumb { | |
| group = new graphic.Group(); | |
| constructor(containerGroup: graphic.Group) { | |
| containerGroup.add(this.group); | |
| } | |
| render( | |
| seriesModel: TreemapSeriesModel, | |
| api: ExtensionAPI, | |
| targetNode: TreeNode, | |
| onSelect: OnSelectCallback | |
| ) { | |
| const model = seriesModel.getModel('breadcrumb'); | |
| const thisGroup = this.group; | |
| thisGroup.removeAll(); | |
| if (!model.get('show') || !targetNode) { | |
| return; | |
| } | |
| const normalStyleModel = model.getModel('itemStyle'); | |
| const emphasisModel = model.getModel('emphasis'); | |
| const textStyleModel = normalStyleModel.getModel('textStyle'); | |
| const emphasisTextStyleModel = emphasisModel.getModel(['itemStyle', 'textStyle']); | |
| const layoutParam: LayoutParam = { | |
| pos: { | |
| left: model.get('left'), | |
| right: model.get('right'), | |
| top: model.get('top'), | |
| bottom: model.get('bottom') | |
| }, | |
| box: { | |
| width: api.getWidth(), | |
| height: api.getHeight() | |
| }, | |
| emptyItemWidth: model.get('emptyItemWidth'), | |
| totalWidth: 0, | |
| renderList: [] | |
| }; | |
| this._prepare(targetNode, layoutParam, textStyleModel); | |
| this._renderContent( | |
| seriesModel, layoutParam, normalStyleModel, | |
| emphasisModel, textStyleModel, emphasisTextStyleModel, onSelect | |
| ); | |
| layout.positionElement(thisGroup, layoutParam.pos, layoutParam.box); | |
| } | |
| /** | |
| * Prepare render list and total width | |
| * @private | |
| */ | |
| _prepare(targetNode: TreeNode, layoutParam: LayoutParam, textStyleModel: BreadcrumbTextStyleModel) { | |
| for (let node = targetNode; node; node = node.parentNode) { | |
| const text = convertOptionIdName(node.getModel<TreemapSeriesNodeItemOption>().get('name'), ''); | |
| const textRect = textStyleModel.getTextRect(text); | |
| const itemWidth = Math.max( | |
| textRect.width + TEXT_PADDING * 2, | |
| layoutParam.emptyItemWidth | |
| ); | |
| layoutParam.totalWidth += itemWidth + ITEM_GAP; | |
| layoutParam.renderList.push({ | |
| node: node, | |
| text: text, | |
| width: itemWidth | |
| }); | |
| } | |
| } | |
| /** | |
| * @private | |
| */ | |
| _renderContent( | |
| seriesModel: TreemapSeriesModel, | |
| layoutParam: LayoutParam, | |
| normalStyleModel: BreadcrumbItemStyleModel, | |
| emphasisModel: BreadcrumbEmphasisItemStyleModel, | |
| textStyleModel: BreadcrumbTextStyleModel, | |
| emphasisTextStyleModel: BreadcrumbTextStyleModel, | |
| onSelect: OnSelectCallback | |
| ) { | |
| // Start rendering. | |
| let lastX = 0; | |
| const emptyItemWidth = layoutParam.emptyItemWidth; | |
| const height = seriesModel.get(['breadcrumb', 'height']); | |
| const availableSize = layout.getAvailableSize(layoutParam.pos, layoutParam.box); | |
| let totalWidth = layoutParam.totalWidth; | |
| const renderList = layoutParam.renderList; | |
| const emphasisItemStyle = emphasisModel.getModel('itemStyle').getItemStyle(); | |
| for (let i = renderList.length - 1; i >= 0; i--) { | |
| const item = renderList[i]; | |
| const itemNode = item.node; | |
| let itemWidth = item.width; | |
| let text = item.text; | |
| // Hdie text and shorten width if necessary. | |
| if (totalWidth > availableSize.width) { | |
| totalWidth -= itemWidth - emptyItemWidth; | |
| itemWidth = emptyItemWidth; | |
| text = null; | |
| } | |
| const el = new graphic.Polygon({ | |
| shape: { | |
| points: makeItemPoints( | |
| lastX, 0, itemWidth, height, | |
| i === renderList.length - 1, i === 0 | |
| ) | |
| }, | |
| style: defaults( | |
| normalStyleModel.getItemStyle(), | |
| { | |
| lineJoin: 'bevel' | |
| } | |
| ), | |
| textContent: new graphic.Text({ | |
| style: createTextStyle(textStyleModel, { text }) | |
| }), | |
| textConfig: { | |
| position: 'inside' | |
| }, | |
| z2: Z2_EMPHASIS_LIFT * 1e4, // A very large z2 | |
| onclick: curry(onSelect, itemNode) | |
| }); | |
| (el as ECElement).disableLabelAnimation = true; | |
| el.getTextContent().ensureState('emphasis').style = createTextStyle(emphasisTextStyleModel, { text }); | |
| el.ensureState('emphasis').style = emphasisItemStyle; | |
| toggleHoverEmphasis( | |
| el, emphasisModel.get('focus'), emphasisModel.get('blurScope'), emphasisModel.get('disabled') | |
| ); | |
| this.group.add(el); | |
| packEventData(el, seriesModel, itemNode); | |
| lastX += itemWidth + ITEM_GAP; | |
| } | |
| } | |
| remove() { | |
| this.group.removeAll(); | |
| } | |
| } | |
| function makeItemPoints(x: number, y: number, itemWidth: number, itemHeight: number, head: boolean, tail: boolean) { | |
| const points = [ | |
| [head ? x : x - ARRAY_LENGTH, y], | |
| [x + itemWidth, y], | |
| [x + itemWidth, y + itemHeight], | |
| [head ? x : x - ARRAY_LENGTH, y + itemHeight] | |
| ]; | |
| !tail && points.splice(2, 0, [x + itemWidth + ARRAY_LENGTH, y + itemHeight / 2]); | |
| !head && points.push([x, y + itemHeight / 2]); | |
| return points; | |
| } | |
| // Package custom mouse event. | |
| function packEventData(el: Element, seriesModel: TreemapSeriesModel, itemNode: TreeNode) { | |
| getECData(el).eventData = { | |
| componentType: 'series', | |
| componentSubType: 'treemap', | |
| componentIndex: seriesModel.componentIndex, | |
| seriesIndex: seriesModel.seriesIndex, | |
| seriesName: seriesModel.name, | |
| seriesType: 'treemap', | |
| selfType: 'breadcrumb', // Distinguish with click event on treemap node. | |
| nodeData: { | |
| dataIndex: itemNode && itemNode.dataIndex, | |
| name: itemNode && itemNode.name | |
| }, | |
| treePathInfo: itemNode && wrapTreePathInfo(itemNode, seriesModel) | |
| }; | |
| } | |
| export default Breadcrumb; |