GraphGLSeries.js 7.6 KB
import * as echarts from 'echarts/lib/echarts';
import createGraphFromNodeEdge from './createGraphFromNodeEdge';
import formatUtil from '../../util/format';

var GraphSeries = echarts.SeriesModel.extend({

    type: 'series.graphGL',

    visualStyleAccessPath: 'itemStyle',

    hasSymbolVisual: true,

    init: function (option) {
        GraphSeries.superApply(this, 'init', arguments);

        // Provide data for legend select
        this.legendDataProvider = function () {
            return this._categoriesData;
        };

        this._updateCategoriesData();
    },

    mergeOption: function (option) {
        GraphSeries.superApply(this, 'mergeOption', arguments);

        this._updateCategoriesData();
    },

    getFormattedLabel: function (dataIndex, status, dataType, dimIndex) {
        var text = formatUtil.getFormattedLabel(this, dataIndex, status, dataType, dimIndex);
        if (text == null) {
            var data = this.getData();
            var lastDim = data.dimensions[data.dimensions.length - 1];
            text = data.get(lastDim, dataIndex);
        }
        return text;
    },

    getInitialData: function (option, ecModel) {
        var edges = option.edges || option.links || [];
        var nodes = option.data || option.nodes || [];
        var self = this;

        if (nodes && edges) {
            return createGraphFromNodeEdge(nodes, edges, this, true, beforeLink).data;
        }

        function beforeLink(nodeData, edgeData) {
            // 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 = ecModel.getModel([]).getModel;
            function newGetModel(path, parentModel) {
                const model = oldGetModel.call(this, path, parentModel);
                model.resolveParentPath = resolveParentPath;
                return model;
            }

            edgeData.wrapMethod('getItemModel', function (model) {
                model.resolveParentPath = resolveParentPath;
                model.getModel = newGetModel;
                return model;
            });

            function resolveParentPath(pathArr) {
                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;
            }
        }
    },

    /**
     * @return {module:echarts/data/Graph}
     */
    getGraph: function () {
        return this.getData().graph;
    },

    /**
     * @return {module:echarts/data/List}
     */
    getEdgeData: function () {
        return this.getGraph().edgeData;
    },

    /**
     * @return {module:echarts/data/List}
     */
    getCategoriesData: function () {
        return this._categoriesData;
    },

    /**
     * @override
     */
    formatTooltip: function (dataIndex, multipleSeries, dataType) {
        if (dataType === 'edge') {
            var nodeData = this.getData();
            var params = this.getDataParams(dataIndex, dataType);
            var edge = nodeData.graph.getEdgeByIndex(dataIndex);
            var sourceName = nodeData.getName(edge.node1.dataIndex);
            var targetName = nodeData.getName(edge.node2.dataIndex);

            var html = [];
            sourceName != null && html.push(sourceName);
            targetName != null && html.push(targetName);
            html = echarts.format.encodeHTML(html.join(' > '));

            if (params.value) {
                html += ' : ' + echarts.format.encodeHTML(params.value);
            }
            return html;
        }
        else { // dataType === 'node' or empty
            return GraphSeries.superApply(this, 'formatTooltip', arguments);
        }
    },

    _updateCategoriesData: function () {
        var categories = (this.option.categories || []).map(function (category) {
            // Data must has value
            return category.value != null ? category : Object.assign({
                value: 0
            }, category);
        });
        var categoriesData = new echarts.List(['value'], this);
        categoriesData.initData(categories);

        this._categoriesData = categoriesData;

        this._categoriesModels = categoriesData.mapArray(function (idx) {
            return categoriesData.getItemModel(idx, true);
        });
    },

    setView: function (payload) {
        if (payload.zoom != null) {
            this.option.zoom = payload.zoom;
        }
        if (payload.offset != null) {
            this.option.offset = payload.offset;
        }
    },

    setNodePosition: function (points) {
        for (var i = 0; i < points.length / 2; i++) {
            var x = points[i * 2];
            var y = points[i * 2 + 1];

            var opt = this.getData().getRawDataItem(i);
            opt.x = x;
            opt.y = y;
        }
    },

    isAnimationEnabled: function () {
        return GraphSeries.superCall(this, 'isAnimationEnabled')
            // Not enable animation when do force layout
            && !(this.get('layout') === 'force' && this.get('force.layoutAnimation'));
    },

    defaultOption: {
        zlevel: 10,
        z: 2,

        legendHoverLink: true,

        // Only support forceAtlas2
        layout: 'forceAtlas2',

        // Configuration of force directed layout
        forceAtlas2: {
            initLayout: null,

            GPU: true,

            steps: 1,

            // barnesHutOptimize

            // Maxp layout steps.
            maxSteps: 1000,

            repulsionByDegree: true,
            linLogMode: false,
            strongGravityMode: false,
            gravity: 1.0,
            // scaling: 1.0,

            edgeWeightInfluence: 1.0,

            // Edge weight range.
            edgeWeight: [1, 4],
            // Node weight range.
            nodeWeight: [1, 4],

            // jitterTolerence: 0.1,
            preventOverlap: false,
            gravityCenter: null
        },

        focusNodeAdjacency: true,

        focusNodeAdjacencyOn: 'mouseover',

        left: 'center',
        top: 'center',
        // right: null,
        // bottom: null,
        // width: '80%',
        // height: '80%',

        symbol: 'circle',
        symbolSize: 5,

        roam: false,

        // Default on center of graph
        center: null,

        zoom: 1,

        // categories: [],

        // data: []
        // Or
        // nodes: []
        //
        // links: []
        // Or
        // edges: []

        label: {
            show: false,
            formatter: '{b}',
            position: 'right',
            distance: 5,
            textStyle: {
                fontSize: 14
            }
        },

        itemStyle: {},

        lineStyle: {
            color: '#aaa',
            width: 1,
            opacity: 0.5
        },

        emphasis: {
            label: {
                show: true
            }
        },

        animation: false
    }
});

export default GraphSeries;