Spaces:
Running
Running
/* | |
* 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' | 'trajectory' | |
} | |
} | |
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; | |