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 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, | |
| 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; | |