|  | /* | 
|  | * 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 SeriesData from '../../data/SeriesData'; | 
|  | import * as zrUtil from 'zrender/src/core/util'; | 
|  | import {defaultEmphasis} from '../../util/model'; | 
|  | import Model from '../../model/Model'; | 
|  | import createGraphFromNodeEdge from '../helper/createGraphFromNodeEdge'; | 
|  | import LegendVisualProvider from '../../visual/LegendVisualProvider'; | 
|  | import { | 
|  | SeriesOption, | 
|  | SeriesOnCartesianOptionMixin, | 
|  | SeriesOnPolarOptionMixin, | 
|  | SeriesOnCalendarOptionMixin, | 
|  | SeriesOnGeoOptionMixin, | 
|  | SeriesOnSingleOptionMixin, | 
|  | OptionDataValue, | 
|  | RoamOptionMixin, | 
|  | SeriesLabelOption, | 
|  | ItemStyleOption, | 
|  | LineStyleOption, | 
|  | SymbolOptionMixin, | 
|  | BoxLayoutOptionMixin, | 
|  | Dictionary, | 
|  | SeriesLineLabelOption, | 
|  | StatesOptionMixin, | 
|  | GraphEdgeItemObject, | 
|  | OptionDataValueNumeric, | 
|  | CallbackDataParams, | 
|  | DefaultEmphasisFocus | 
|  | } from '../../util/types'; | 
|  | import SeriesModel from '../../model/Series'; | 
|  | import Graph from '../../data/Graph'; | 
|  | import GlobalModel from '../../model/Global'; | 
|  | import { VectorArray } from 'zrender/src/core/vector'; | 
|  | import { ForceLayoutInstance } from './forceLayout'; | 
|  | import { LineDataVisual } from '../../visual/commonVisualTypes'; | 
|  | import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; | 
|  | import { defaultSeriesFormatTooltip } from '../../component/tooltip/seriesFormatTooltip'; | 
|  | import {initCurvenessList, createEdgeMapForCurveness} from '../helper/multipleGraphEdgeHelper'; | 
|  |  | 
|  |  | 
|  | type GraphDataValue = OptionDataValue | OptionDataValue[]; | 
|  |  | 
|  | interface GraphEdgeLineStyleOption extends LineStyleOption { | 
|  | curveness?: number | 
|  | } | 
|  |  | 
|  | export interface GraphNodeStateOption<TCbParams = never> { | 
|  | itemStyle?: ItemStyleOption<TCbParams> | 
|  | label?: SeriesLabelOption | 
|  | } | 
|  |  | 
|  |  | 
|  | interface ExtraEmphasisState { | 
|  | focus?: DefaultEmphasisFocus | 'adjacency' | 
|  | } | 
|  | interface GraphNodeStatesMixin { | 
|  | emphasis?: ExtraEmphasisState | 
|  | } | 
|  |  | 
|  | interface GraphEdgeStatesMixin { | 
|  | emphasis?: ExtraEmphasisState | 
|  | } | 
|  |  | 
|  | export interface GraphNodeItemOption extends SymbolOptionMixin, GraphNodeStateOption, | 
|  | GraphNodeStateOption, StatesOptionMixin<GraphNodeStateOption, GraphNodeStatesMixin> { | 
|  |  | 
|  | id?: string | 
|  | name?: string | 
|  | value?: GraphDataValue | 
|  |  | 
|  | /** | 
|  | * Fixed x position | 
|  | */ | 
|  | x?: number | 
|  | /** | 
|  | * Fixed y position | 
|  | */ | 
|  | y?: number | 
|  |  | 
|  | /** | 
|  | * If this node is fixed during force layout. | 
|  | */ | 
|  | fixed?: boolean | 
|  |  | 
|  | /** | 
|  | * Index or name of category | 
|  | */ | 
|  | category?: number | string | 
|  |  | 
|  | draggable?: boolean | 
|  | cursor?: string | 
|  | } | 
|  |  | 
|  | export interface GraphEdgeStateOption { | 
|  | lineStyle?: GraphEdgeLineStyleOption | 
|  | label?: SeriesLineLabelOption | 
|  | } | 
|  | export interface GraphEdgeItemOption extends | 
|  | GraphEdgeStateOption, | 
|  | StatesOptionMixin<GraphEdgeStateOption, GraphEdgeStatesMixin>, | 
|  | GraphEdgeItemObject<OptionDataValueNumeric> { | 
|  |  | 
|  | value?: number | 
|  |  | 
|  | /** | 
|  | * Symbol of both line ends | 
|  | */ | 
|  | symbol?: string | string[] | 
|  |  | 
|  | symbolSize?: number | number[] | 
|  |  | 
|  | ignoreForceLayout?: boolean | 
|  | } | 
|  |  | 
|  | export interface GraphCategoryItemOption extends SymbolOptionMixin, | 
|  | GraphNodeStateOption, StatesOptionMixin<GraphNodeStateOption, GraphNodeStatesMixin> { | 
|  | name?: string | 
|  |  | 
|  | value?: OptionDataValue | 
|  | } | 
|  |  | 
|  | export interface GraphSeriesOption | 
|  | extends SeriesOption<GraphNodeStateOption<CallbackDataParams>, GraphNodeStatesMixin>, | 
|  | SeriesOnCartesianOptionMixin, SeriesOnPolarOptionMixin, SeriesOnCalendarOptionMixin, | 
|  | SeriesOnGeoOptionMixin, SeriesOnSingleOptionMixin, | 
|  | SymbolOptionMixin<CallbackDataParams>, | 
|  | RoamOptionMixin, | 
|  | BoxLayoutOptionMixin { | 
|  |  | 
|  | type?: 'graph' | 
|  |  | 
|  | coordinateSystem?: string | 
|  |  | 
|  | legendHoverLink?: boolean | 
|  |  | 
|  | layout?: 'none' | 'force' | 'circular' | 
|  |  | 
|  | data?: (GraphNodeItemOption | GraphDataValue)[] | 
|  | nodes?: (GraphNodeItemOption | GraphDataValue)[] | 
|  |  | 
|  | edges?: GraphEdgeItemOption[] | 
|  | links?: GraphEdgeItemOption[] | 
|  |  | 
|  | categories?: GraphCategoryItemOption[] | 
|  |  | 
|  | /** | 
|  | * @deprecated | 
|  | */ | 
|  | focusNodeAdjacency?: boolean | 
|  |  | 
|  | /** | 
|  | * Symbol size scale ratio in roam | 
|  | */ | 
|  | nodeScaleRatio?: 0.6, | 
|  |  | 
|  | draggable?: boolean | 
|  |  | 
|  | edgeSymbol?: string | string[] | 
|  | edgeSymbolSize?: number | number[] | 
|  |  | 
|  | edgeLabel?: SeriesLineLabelOption | 
|  | label?: SeriesLabelOption | 
|  |  | 
|  | itemStyle?: ItemStyleOption<CallbackDataParams> | 
|  | lineStyle?: GraphEdgeLineStyleOption | 
|  |  | 
|  | emphasis?: { | 
|  | focus?: Exclude<GraphNodeItemOption['emphasis'], undefined>['focus'] | 
|  | scale?: boolean | number | 
|  | label?: SeriesLabelOption | 
|  | edgeLabel?: SeriesLabelOption | 
|  | itemStyle?: ItemStyleOption | 
|  | lineStyle?: LineStyleOption | 
|  | } | 
|  |  | 
|  | blur?: { | 
|  | label?: SeriesLabelOption | 
|  | edgeLabel?: SeriesLabelOption | 
|  | itemStyle?: ItemStyleOption | 
|  | lineStyle?: LineStyleOption | 
|  | } | 
|  |  | 
|  | select?: { | 
|  | label?: SeriesLabelOption | 
|  | edgeLabel?: SeriesLabelOption | 
|  | itemStyle?: ItemStyleOption | 
|  | lineStyle?: LineStyleOption | 
|  | } | 
|  |  | 
|  | // Configuration of circular layout | 
|  | circular?: { | 
|  | rotateLabel?: boolean | 
|  | } | 
|  |  | 
|  | // Configuration of force directed layout | 
|  | force?: { | 
|  | initLayout?: 'circular' | 'none' | 
|  | // Node repulsion. Can be an array to represent range. | 
|  | repulsion?: number | number[] | 
|  | gravity?: number | 
|  | // Initial friction | 
|  | friction?: number | 
|  |  | 
|  | // Edge length. Can be an array to represent range. | 
|  | edgeLength?: number | number[] | 
|  |  | 
|  | layoutAnimation?: boolean | 
|  | } | 
|  |  | 
|  | /** | 
|  | * auto curveness for multiple edge, invalid when `lineStyle.curveness` is set | 
|  | */ | 
|  | autoCurveness?: boolean | number | number[] | 
|  | } | 
|  |  | 
|  | class GraphSeriesModel extends SeriesModel<GraphSeriesOption> { | 
|  | static readonly type = 'series.graph'; | 
|  | readonly type = GraphSeriesModel.type; | 
|  |  | 
|  | static readonly dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar']; | 
|  |  | 
|  | private _categoriesData: SeriesData; | 
|  | private _categoriesModels: Model<GraphCategoryItemOption>[]; | 
|  |  | 
|  | /** | 
|  | * Preserved points during layouting | 
|  | */ | 
|  | preservedPoints?: Dictionary<VectorArray>; | 
|  |  | 
|  | forceLayout?: ForceLayoutInstance; | 
|  |  | 
|  | hasSymbolVisual = true; | 
|  |  | 
|  | init(option: GraphSeriesOption) { | 
|  | super.init.apply(this, arguments as any); | 
|  |  | 
|  | const self = this; | 
|  | function getCategoriesData() { | 
|  | return self._categoriesData; | 
|  | } | 
|  | // Provide data for legend select | 
|  | this.legendVisualProvider = new LegendVisualProvider( | 
|  | getCategoriesData, getCategoriesData | 
|  | ); | 
|  |  | 
|  | this.fillDataTextStyle(option.edges || option.links); | 
|  |  | 
|  | this._updateCategoriesData(); | 
|  | } | 
|  |  | 
|  | mergeOption(option: GraphSeriesOption) { | 
|  | super.mergeOption.apply(this, arguments as any); | 
|  |  | 
|  | this.fillDataTextStyle(option.edges || option.links); | 
|  |  | 
|  | this._updateCategoriesData(); | 
|  | } | 
|  |  | 
|  | mergeDefaultAndTheme(option: GraphSeriesOption) { | 
|  | super.mergeDefaultAndTheme.apply(this, arguments as any); | 
|  | defaultEmphasis(option, 'edgeLabel', ['show']); | 
|  | } | 
|  |  | 
|  | getInitialData(option: GraphSeriesOption, ecModel: GlobalModel): SeriesData { | 
|  | const edges = option.edges || option.links || []; | 
|  | const nodes = option.data || option.nodes || []; | 
|  | const self = this; | 
|  |  | 
|  | if (nodes && edges) { | 
|  | // auto curveness | 
|  | initCurvenessList(this); | 
|  | const graph = createGraphFromNodeEdge(nodes as GraphNodeItemOption[], edges, this, true, beforeLink); | 
|  | zrUtil.each(graph.edges, function (edge) { | 
|  | createEdgeMapForCurveness(edge.node1, edge.node2, this, edge.dataIndex); | 
|  | }, this); | 
|  | return graph.data; | 
|  | } | 
|  |  | 
|  | function beforeLink(nodeData: SeriesData, edgeData: SeriesData) { | 
|  | // Overwrite nodeData.getItemModel to | 
|  | nodeData.wrapMethod('getItemModel', function (model) { | 
|  | const categoriesModels = self._categoriesModels; | 
|  | const categoryIdx = model.getShallow('category'); | 
|  | const categoryModel = categoriesModels[categoryIdx]; | 
|  | if (categoryModel) { | 
|  | categoryModel.parentModel = model.parentModel; | 
|  | model.parentModel = categoryModel; | 
|  | } | 
|  | return model; | 
|  | }); | 
|  |  | 
|  | // TODO Inherit resolveParentPath by default in Model#getModel? | 
|  | const oldGetModel = Model.prototype.getModel; | 
|  | function newGetModel(this: Model, path: any, parentModel?: Model) { | 
|  | const model = oldGetModel.call(this, path, parentModel); | 
|  | model.resolveParentPath = resolveParentPath; | 
|  | return model; | 
|  | } | 
|  |  | 
|  | edgeData.wrapMethod('getItemModel', function (model: Model) { | 
|  | model.resolveParentPath = resolveParentPath; | 
|  | model.getModel = newGetModel; | 
|  | return model; | 
|  | }); | 
|  |  | 
|  | function resolveParentPath(this: Model, pathArr: readonly string[]): string[] { | 
|  | if (pathArr && (pathArr[0] === 'label' || pathArr[1] === 'label')) { | 
|  | const newPathArr = pathArr.slice(); | 
|  | if (pathArr[0] === 'label') { | 
|  | newPathArr[0] = 'edgeLabel'; | 
|  | } | 
|  | else if (pathArr[1] === 'label') { | 
|  | newPathArr[1] = 'edgeLabel'; | 
|  | } | 
|  | return newPathArr; | 
|  | } | 
|  | return pathArr as string[]; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | getGraph(): Graph { | 
|  | return this.getData().graph; | 
|  | } | 
|  |  | 
|  | getEdgeData() { | 
|  | return this.getGraph().edgeData as SeriesData<GraphSeriesModel, LineDataVisual>; | 
|  | } | 
|  |  | 
|  | getCategoriesData(): SeriesData { | 
|  | return this._categoriesData; | 
|  | } | 
|  |  | 
|  | formatTooltip( | 
|  | dataIndex: number, | 
|  | multipleSeries: boolean, | 
|  | dataType: string | 
|  | ) { | 
|  | if (dataType === 'edge') { | 
|  | const nodeData = this.getData(); | 
|  | const params = this.getDataParams(dataIndex, dataType); | 
|  | const edge = nodeData.graph.getEdgeByIndex(dataIndex); | 
|  | const sourceName = nodeData.getName(edge.node1.dataIndex); | 
|  | const targetName = nodeData.getName(edge.node2.dataIndex); | 
|  |  | 
|  | const nameArr = []; | 
|  | sourceName != null && nameArr.push(sourceName); | 
|  | targetName != null && nameArr.push(targetName); | 
|  |  | 
|  | return createTooltipMarkup('nameValue', { | 
|  | name: nameArr.join(' > '), | 
|  | value: params.value, | 
|  | noValue: params.value == null | 
|  | }); | 
|  | } | 
|  | // dataType === 'node' or empty | 
|  | const nodeMarkup = defaultSeriesFormatTooltip({ | 
|  | series: this, | 
|  | dataIndex: dataIndex, | 
|  | multipleSeries: multipleSeries | 
|  | }); | 
|  | return nodeMarkup; | 
|  | } | 
|  |  | 
|  | _updateCategoriesData() { | 
|  | const categories = zrUtil.map(this.option.categories || [], function (category) { | 
|  | // Data must has value | 
|  | return category.value != null ? category : zrUtil.extend({ | 
|  | value: 0 | 
|  | }, category); | 
|  | }); | 
|  | const categoriesData = new SeriesData(['value'], this); | 
|  | categoriesData.initData(categories); | 
|  |  | 
|  | this._categoriesData = categoriesData; | 
|  |  | 
|  | this._categoriesModels = categoriesData.mapArray(function (idx) { | 
|  | return categoriesData.getItemModel(idx); | 
|  | }); | 
|  | } | 
|  |  | 
|  | setZoom(zoom: number) { | 
|  | this.option.zoom = zoom; | 
|  | } | 
|  |  | 
|  | setCenter(center: number[]) { | 
|  | this.option.center = center; | 
|  | } | 
|  |  | 
|  | isAnimationEnabled() { | 
|  | return super.isAnimationEnabled() | 
|  | // Not enable animation when do force layout | 
|  | && !(this.get('layout') === 'force' && this.get(['force', 'layoutAnimation'])); | 
|  | } | 
|  |  | 
|  | static defaultOption: GraphSeriesOption = { | 
|  | // zlevel: 0, | 
|  | z: 2, | 
|  |  | 
|  | coordinateSystem: 'view', | 
|  |  | 
|  | // Default option for all coordinate systems | 
|  | // xAxisIndex: 0, | 
|  | // yAxisIndex: 0, | 
|  | // polarIndex: 0, | 
|  | // geoIndex: 0, | 
|  |  | 
|  | legendHoverLink: true, | 
|  |  | 
|  | layout: null, | 
|  |  | 
|  | // Configuration of circular layout | 
|  | circular: { | 
|  | rotateLabel: false | 
|  | }, | 
|  | // Configuration of force directed layout | 
|  | force: { | 
|  | initLayout: null, | 
|  | // Node repulsion. Can be an array to represent range. | 
|  | repulsion: [0, 50], | 
|  | gravity: 0.1, | 
|  | // Initial friction | 
|  | friction: 0.6, | 
|  |  | 
|  | // Edge length. Can be an array to represent range. | 
|  | edgeLength: 30, | 
|  |  | 
|  | layoutAnimation: true | 
|  | }, | 
|  |  | 
|  | left: 'center', | 
|  | top: 'center', | 
|  | // right: null, | 
|  | // bottom: null, | 
|  | // width: '80%', | 
|  | // height: '80%', | 
|  |  | 
|  | symbol: 'circle', | 
|  | symbolSize: 10, | 
|  |  | 
|  | edgeSymbol: ['none', 'none'], | 
|  | edgeSymbolSize: 10, | 
|  | edgeLabel: { | 
|  | position: 'middle', | 
|  | distance: 5 | 
|  | }, | 
|  |  | 
|  | draggable: false, | 
|  |  | 
|  | roam: false, | 
|  |  | 
|  | // Default on center of graph | 
|  | center: null, | 
|  |  | 
|  | zoom: 1, | 
|  | // Symbol size scale ratio in roam | 
|  | nodeScaleRatio: 0.6, | 
|  |  | 
|  | // cursor: null, | 
|  |  | 
|  | // categories: [], | 
|  |  | 
|  | // data: [] | 
|  | // Or | 
|  | // nodes: [] | 
|  | // | 
|  | // links: [] | 
|  | // Or | 
|  | // edges: [] | 
|  |  | 
|  | label: { | 
|  | show: false, | 
|  | formatter: '{b}' | 
|  | }, | 
|  |  | 
|  | itemStyle: {}, | 
|  |  | 
|  | lineStyle: { | 
|  | color: '#aaa', | 
|  | width: 1, | 
|  | opacity: 0.5 | 
|  | }, | 
|  | emphasis: { | 
|  | scale: true, | 
|  | label: { | 
|  | show: true | 
|  | } | 
|  | }, | 
|  |  | 
|  | select: { | 
|  | itemStyle: { | 
|  | borderColor: '#212121' | 
|  | } | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | export default GraphSeriesModel; |