blob: c5589ee2532e13bfc32fe18e01c538c95461f5e5 [file] [log] [blame]
/*
* 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 { Z2_EMPHASIS_LIFT } from '../../util/states';
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 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');
// let emphasisStyleModel = model.getModel('emphasis.itemStyle');
const textStyleModel = normalStyleModel.getModel('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, textStyleModel, 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,
textStyleModel: 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;
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: {
text,
fill: textStyleModel.getTextColor(),
font: textStyleModel.getFont()
}
}),
textConfig: {
position: 'inside'
},
z2: Z2_EMPHASIS_LIFT * 1e4, // A very large z2
onclick: curry(onSelect, itemNode)
});
(el as ECElement).disableLabelAnimation = true;
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.componentIndex,
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;