/* * 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; type BreadcrumbEmphasisItemStyleModel = Model; type BreadcrumbTextStyleModel = Model; 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().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;