| /* | 
 | * 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; |