| /* |
| * 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 SeriesModel from '../../model/Series'; |
| import createGraphFromNodeEdge from '../helper/createGraphFromNodeEdge'; |
| import Model from '../../model/Model'; |
| import { |
| SeriesOption, |
| BoxLayoutOptionMixin, |
| OptionDataValue, |
| SeriesLabelOption, |
| ItemStyleOption, |
| LineStyleOption, |
| LayoutOrient, |
| ColorString, |
| StatesOptionMixin, |
| OptionDataItemObject, |
| GraphEdgeItemObject, |
| OptionDataValueNumeric, |
| DefaultEmphasisFocus |
| } from '../../util/types'; |
| import GlobalModel from '../../model/Global'; |
| import List from '../../data/List'; |
| import { LayoutRect } from '../../util/layout'; |
| import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; |
| |
| |
| type FocusNodeAdjacency = boolean | 'inEdges' | 'outEdges' | 'allEdges'; |
| |
| export interface SankeyNodeStateOption { |
| label?: SeriesLabelOption |
| itemStyle?: ItemStyleOption |
| } |
| |
| export interface SankeyEdgeStateOption { |
| lineStyle?: SankeyEdgeStyleOption |
| } |
| |
| interface SankeyBothStateOption extends SankeyNodeStateOption, SankeyEdgeStateOption { |
| } |
| |
| interface SankeyEdgeStyleOption extends LineStyleOption { |
| curveness?: number |
| } |
| |
| interface ExtraStateOption { |
| emphasis?: { |
| focus?: DefaultEmphasisFocus | 'adjacency' |
| } |
| } |
| |
| export interface SankeyNodeItemOption extends SankeyNodeStateOption, |
| StatesOptionMixin<SankeyNodeStateOption, ExtraStateOption>, |
| OptionDataItemObject<OptionDataValue> { |
| id?: string |
| |
| localX?: number |
| localY?: number |
| |
| depth?: number |
| |
| draggable?: boolean |
| |
| focusNodeAdjacency?: FocusNodeAdjacency |
| } |
| |
| export interface SankeyEdgeItemOption extends |
| SankeyEdgeStateOption, |
| StatesOptionMixin<SankeyEdgeStateOption, ExtraStateOption>, |
| GraphEdgeItemObject<OptionDataValueNumeric> { |
| focusNodeAdjacency?: FocusNodeAdjacency |
| } |
| |
| export interface SankeyLevelOption extends SankeyNodeStateOption, SankeyEdgeStateOption { |
| depth: number |
| } |
| |
| export interface SankeySeriesOption |
| extends SeriesOption<SankeyBothStateOption, ExtraStateOption>, SankeyBothStateOption, |
| BoxLayoutOptionMixin { |
| type?: 'sankey' |
| |
| /** |
| * color will be linear mapped. |
| */ |
| color?: ColorString[] |
| |
| coordinateSystem?: 'view' |
| |
| orient?: LayoutOrient |
| /** |
| * The width of the node |
| */ |
| nodeWidth?: number |
| /** |
| * The vertical distance between two nodes |
| */ |
| nodeGap?: number |
| |
| /** |
| * Control if the node can move or not |
| */ |
| draggable?: boolean |
| /** |
| * Will be allEdges if true. |
| * @deprecated |
| */ |
| focusNodeAdjacency?: FocusNodeAdjacency |
| /** |
| * The number of iterations to change the position of the node |
| */ |
| layoutIterations?: number |
| |
| nodeAlign?: 'justify' | 'left' | 'right' // TODO justify should be auto |
| |
| data?: SankeyNodeItemOption[] |
| nodes?: SankeyNodeItemOption[] |
| |
| edges?: SankeyEdgeItemOption[] |
| links?: SankeyEdgeItemOption[] |
| |
| levels?: SankeyLevelOption[] |
| } |
| |
| class SankeySeriesModel extends SeriesModel<SankeySeriesOption> { |
| static readonly type = 'series.sankey'; |
| readonly type = SankeySeriesModel.type; |
| |
| levelModels: Model<SankeyLevelOption>[]; |
| |
| layoutInfo: LayoutRect; |
| |
| /** |
| * Init a graph data structure from data in option series |
| * |
| * @param {Object} option the object used to config echarts view |
| * @return {module:echarts/data/List} storage initial data |
| */ |
| getInitialData(option: SankeySeriesOption, ecModel: GlobalModel) { |
| const links = option.edges || option.links; |
| const nodes = option.data || option.nodes; |
| const levels = option.levels; |
| this.levelModels = []; |
| const levelModels = this.levelModels; |
| |
| for (let i = 0; i < levels.length; i++) { |
| if (levels[i].depth != null && levels[i].depth >= 0) { |
| levelModels[levels[i].depth] = new Model(levels[i], this, ecModel); |
| } |
| else { |
| if (__DEV__) { |
| throw new Error('levels[i].depth is mandatory and should be natural number'); |
| } |
| } |
| } |
| if (nodes && links) { |
| const graph = createGraphFromNodeEdge(nodes, links, this, true, beforeLink); |
| return graph.data; |
| } |
| function beforeLink(nodeData: List, edgeData: List) { |
| nodeData.wrapMethod('getItemModel', function (model: Model, idx: number) { |
| const seriesModel = model.parentModel as SankeySeriesModel; |
| const layout = seriesModel.getData().getItemLayout(idx); |
| if (layout) { |
| const nodeDepth = layout.depth; |
| const levelModel = seriesModel.levelModels[nodeDepth]; |
| if (levelModel) { |
| model.parentModel = levelModel; |
| } |
| } |
| return model; |
| }); |
| |
| edgeData.wrapMethod('getItemModel', function (model: Model, idx: number) { |
| const seriesModel = model.parentModel as SankeySeriesModel; |
| const edge = seriesModel.getGraph().getEdgeByIndex(idx); |
| const layout = edge.node1.getLayout(); |
| if (layout) { |
| const depth = layout.depth; |
| const levelModel = seriesModel.levelModels[depth]; |
| if (levelModel) { |
| model.parentModel = levelModel; |
| } |
| } |
| return model; |
| }); |
| } |
| } |
| |
| setNodePosition(dataIndex: number, localPosition: number[]) { |
| const nodes = this.option.data || this.option.nodes; |
| const dataItem = nodes[dataIndex]; |
| dataItem.localX = localPosition[0]; |
| dataItem.localY = localPosition[1]; |
| } |
| |
| /** |
| * Return the graphic data structure |
| * |
| * @return graphic data structure |
| */ |
| getGraph() { |
| return this.getData().graph; |
| } |
| |
| /** |
| * Get edge data of graphic data structure |
| * |
| * @return data structure of list |
| */ |
| getEdgeData() { |
| return this.getGraph().edgeData; |
| } |
| |
| formatTooltip( |
| dataIndex: number, |
| multipleSeries: boolean, |
| dataType: 'node' | 'edge' |
| ) { |
| function noValue(val: unknown): boolean { |
| return isNaN(val as number) || val == null; |
| } |
| // dataType === 'node' or empty do not show tooltip by default |
| if (dataType === 'edge') { |
| const params = this.getDataParams(dataIndex, dataType); |
| const rawDataOpt = params.data as SankeyEdgeItemOption; |
| const edgeValue = params.value; |
| const edgeName = rawDataOpt.source + ' -- ' + rawDataOpt.target; |
| return createTooltipMarkup('nameValue', { |
| name: edgeName, |
| value: edgeValue, |
| noValue: noValue(edgeValue) |
| }); |
| } |
| // dataType === 'node' |
| else { |
| const node = this.getGraph().getNodeByIndex(dataIndex); |
| const value = node.getLayout().value; |
| const name = (this.getDataParams(dataIndex, dataType).data as SankeyNodeItemOption).name; |
| return createTooltipMarkup('nameValue', { |
| name: name != null ? name + '' : null, |
| value: value, |
| noValue: noValue(value) |
| }); |
| } |
| } |
| |
| optionUpdated() {} |
| |
| // Override Series.getDataParams() |
| getDataParams(dataIndex: number, dataType: 'node' | 'edge') { |
| const params = super.getDataParams(dataIndex, dataType); |
| if (params.value == null && dataType === 'node') { |
| const node = this.getGraph().getNodeByIndex(dataIndex); |
| const nodeValue = node.getLayout().value; |
| params.value = nodeValue; |
| } |
| return params; |
| } |
| |
| static defaultOption: SankeySeriesOption = { |
| zlevel: 0, |
| z: 2, |
| |
| coordinateSystem: 'view', |
| |
| left: '5%', |
| top: '5%', |
| right: '20%', |
| bottom: '5%', |
| |
| orient: 'horizontal', |
| |
| nodeWidth: 20, |
| |
| nodeGap: 8, |
| draggable: true, |
| |
| layoutIterations: 32, |
| |
| label: { |
| show: true, |
| position: 'right', |
| fontSize: 12 |
| }, |
| |
| levels: [], |
| |
| nodeAlign: 'justify', |
| |
| lineStyle: { |
| color: '#314656', |
| opacity: 0.2, |
| curveness: 0.5 |
| }, |
| |
| emphasis: { |
| label: { |
| show: true |
| }, |
| lineStyle: { |
| opacity: 0.5 |
| } |
| }, |
| |
| select: { |
| itemStyle: { |
| borderColor: '#212121' |
| } |
| }, |
| |
| animationEasing: 'linear', |
| |
| animationDuration: 1000 |
| }; |
| } |
| |
| export default SankeySeriesModel; |