|  | /* | 
|  | * 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, | 
|  | CallbackDataParams | 
|  | } from '../../util/types'; | 
|  | import GlobalModel from '../../model/Global'; | 
|  | import SeriesData from '../../data/SeriesData'; | 
|  | import { LayoutRect } from '../../util/layout'; | 
|  | import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; | 
|  |  | 
|  |  | 
|  | type FocusNodeAdjacency = boolean | 'inEdges' | 'outEdges' | 'allEdges'; | 
|  |  | 
|  | export interface SankeyNodeStateOption<TCbParams = never> { | 
|  | label?: SeriesLabelOption | 
|  | itemStyle?: ItemStyleOption<TCbParams> | 
|  | } | 
|  |  | 
|  | export interface SankeyEdgeStateOption { | 
|  | lineStyle?: SankeyEdgeStyleOption | 
|  | } | 
|  |  | 
|  | interface SankeyBothStateOption<TCbParams> extends SankeyNodeStateOption<TCbParams>, 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 | 
|  | edgeLabel?: SeriesLabelOption | 
|  | } | 
|  |  | 
|  | export interface SankeyLevelOption extends SankeyNodeStateOption, SankeyEdgeStateOption { | 
|  | depth: number | 
|  | } | 
|  |  | 
|  | export interface SankeySeriesOption | 
|  | extends SeriesOption<SankeyBothStateOption<CallbackDataParams>, ExtraStateOption>, | 
|  | SankeyBothStateOption<CallbackDataParams>, | 
|  | 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[] | 
|  |  | 
|  | edgeLabel?: SeriesLabelOption & { | 
|  | position?: 'inside' | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 | 
|  | */ | 
|  | 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: SeriesData, edgeData: SeriesData) { | 
|  | 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 | 
|  | }, | 
|  |  | 
|  | edgeLabel: { | 
|  | show: false, | 
|  | 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; |