apache / echarts

Apache ECharts is a powerful, interactive charting and data visualization library for browser
https://echarts.apache.org
Apache License 2.0
60.45k stars 19.61k forks source link

Echarts关系图(Graph)能否获取到力矢量布局(force)后点(node)的[x, y]坐标点? #11807

Closed Caitingwei closed 2 years ago

Caitingwei commented 4 years ago

What problem does this feature solve?

项目中有一个需求,后面的渲染需要固定某个点。但初始化使用了力矢量布局(force)。我并不知道其布局出来的xy轴坐标。导致下次渲染时不能固定该点。

node: 
{
   fixed: true,
   x: ?,  // 因为想固定该点,但不知道力矢量布局(force)后的x,y坐标
   y: ?,
}

What does the proposed API look like?

希望在力矢量布局完成后再chart.getOption() api中返回力矢量布局(force)后的x,y坐标

echarts-bot[bot] commented 4 years ago

Hi! We've received your issue and please be patient to get responded. 🎉 The average response time is expected to be within one day for weekdays.

In the meanwhile, please make sure that you have posted enough image to demo your request. You may also check out the API and chart option to get the answer.

If you don't get helped for a long time (over a week) or have an urgent question to ask, you may also send an email to dev@echarts.apache.org. Please attach the issue link if it's a technical questions.

If you are interested in the project, you may also subscribe our mail list.

Have a nice day! 🍵

Ovilia commented 4 years ago

目前没有这样的接口,不过好像印象中之前也有别人提过这样的需求,可以考虑是否作为一个新接口提供

Caitingwei commented 4 years ago

@Ovilia 好的谢谢,我目前项目需要使用力矢量布局(force)后的x,y坐标。请问可以在源码哪里修改获得吗?或者思路。谢谢

Caitingwei commented 4 years ago

非常感谢,我通过修改lib/chart/graph/GraphView的render方法,拿到了力矢量布局的x,y坐标。 通过Graphinstance.getOption()可以获取到x,y坐标。 代码如下:

import * as GraphViewInstance from 'echarts/lib/chart/graph/GraphView';
import graphic from "echarts/lib/util/graphic";
import adjustEdge from 'echarts/lib/chart/graph/adjustEdge';
const getNodeGlobalScale = require("echarts/lib/chart/graph/graphHelper").getNodeGlobalScale;
var FOCUS_ADJACENCY = '__focusNodeAdjacency';
var UNFOCUS_ADJACENCY = '__unfocusNodeAdjacency';

GraphViewInstance.prototype.render = function (seriesModel, ecModel, api) {
  var coordSys = seriesModel.coordinateSystem;
  this._model = seriesModel;
  var symbolDraw = this._symbolDraw;
  var lineDraw = this._lineDraw;
  var group = this.group;

  if (coordSys.type === 'view') {
    var groupNewProp = {
      position: coordSys.position,
      scale: coordSys.scale
    };

    if (this._firstRender) {
      group.attr(groupNewProp);
    } else {
      graphic.updateProps(group, groupNewProp, seriesModel);
    }
  } // Fix edge contact point with node

  adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel));
  var data = seriesModel.getData();
  symbolDraw.updateData(data);
  var edgeData = seriesModel.getEdgeData();
  lineDraw.updateData(edgeData);

  this._updateNodeAndLinkScale();
  /** 在原始数据里写入layout的x,y坐标 */
  if (data._rawData && data._rawData._data) {
    data._rawData._data.forEach(function(item, _index) {
      if (data._graphicEls[_index]) {
        if (data._rawData._data[_index] && typeof data._rawData._data[_index].x !== 'number') {
          data._rawData._data[_index].x = data._graphicEls[_index].position[0];
        }
        if (data._rawData._data[_index] && typeof data._rawData._data[_index].y !== 'number') {
          data._rawData._data[_index].y = data._graphicEls[_index].position[1];
        }
      }
    })
  }
  this._updateController(seriesModel, ecModel, api);

  clearTimeout(this._layoutTimeout);
  var forceLayout = seriesModel.forceLayout;
  var layoutAnimation = seriesModel.get('force.layoutAnimation');

  if (forceLayout) {
    this._startForceLayoutIteration(forceLayout, layoutAnimation);
  }

  data.eachItemGraphicEl(function (el, idx) {
    var itemModel = data.getItemModel(idx); // Update draggable

    el.off('drag').off('dragend');
    var draggable = itemModel.get('draggable');

    if (draggable) {
      el.on('drag', function () {
        if (forceLayout) {
          forceLayout.warmUp();
          !this._layouting && this._startForceLayoutIteration(forceLayout, layoutAnimation);
          forceLayout.setFixed(idx); // Write position back to layout

          data.setItemLayout(idx, el.position);
        }
      }, this).on('dragend', function () {
        if (forceLayout) {
          forceLayout.setUnfixed(idx);
        }
      }, this);
    }

    el.setDraggable(draggable && forceLayout);
    el[FOCUS_ADJACENCY] && el.off('mouseover', el[FOCUS_ADJACENCY]);
    el[UNFOCUS_ADJACENCY] && el.off('mouseout', el[UNFOCUS_ADJACENCY]);

    if (itemModel.get('focusNodeAdjacency')) {
      el.on('mouseover', el[FOCUS_ADJACENCY] = function () {
        api.dispatchAction({
          type: 'focusNodeAdjacency',
          seriesId: seriesModel.id,
          dataIndex: el.dataIndex
        });
      });
      el.on('mouseout', el[UNFOCUS_ADJACENCY] = function () {
        api.dispatchAction({
          type: 'unfocusNodeAdjacency',
          seriesId: seriesModel.id
        });
      });
    }
  }, this);
  data.graph.eachEdge(function (edge) {
    var el = edge.getGraphicEl();
    el[FOCUS_ADJACENCY] && el.off('mouseover', el[FOCUS_ADJACENCY]);
    el[UNFOCUS_ADJACENCY] && el.off('mouseout', el[UNFOCUS_ADJACENCY]);

    if (edge.getModel().get('focusNodeAdjacency')) {
      el.on('mouseover', el[FOCUS_ADJACENCY] = function () {
        api.dispatchAction({
          type: 'focusNodeAdjacency',
          seriesId: seriesModel.id,
          edgeDataIndex: edge.dataIndex
        });
      });
      el.on('mouseout', el[UNFOCUS_ADJACENCY] = function () {
        api.dispatchAction({
          type: 'unfocusNodeAdjacency',
          seriesId: seriesModel.id
        });
      });
    }
  });
  var circularRotateLabel = seriesModel.get('layout') === 'circular' && seriesModel.get('circular.rotateLabel');
  var cx = data.getLayout('cx');
  var cy = data.getLayout('cy');
  data.eachItemGraphicEl(function (el, idx) {
    var itemModel = data.getItemModel(idx);
    var labelRotate = itemModel.get('label.rotate') || 0;
    var symbolPath = el.getSymbolPath();

    if (circularRotateLabel) {
      var pos = data.getItemLayout(idx);
      var rad = Math.atan2(pos[1] - cy, pos[0] - cx);

      if (rad < 0) {
        rad = Math.PI * 2 + rad;
      }

      var isLeft = pos[0] < cx;

      if (isLeft) {
        rad = rad - Math.PI;
      }

      var textPosition = isLeft ? 'left' : 'right';
      graphic.modifyLabelStyle(symbolPath, {
        textRotation: -rad,
        textPosition: textPosition,
        textOrigin: 'center'
      }, {
        textPosition: textPosition
      });
    } else {
      graphic.modifyLabelStyle(symbolPath, {
        textRotation: labelRotate *= Math.PI / 180
      });
    }
  });
  this._firstRender = false;
}
pissang commented 4 years ago

@Caitingwei 赞,是否可以给我们提一个把点存储到 option 中的 PR

现在的位置缓存更新是在 https://github.com/apache/incubator-echarts/blob/master/src/chart/graph/forceLayout.js#L116 这里

可以在这里改成把位置写入 option

travis1111 commented 4 years ago

@Caitingwei 我也需要这个功能,能提供一个 PR 吗?谢谢!

travis1111 commented 4 years ago

@pissang 我刚刚试了下,我在你说的位置拿到了最新的位置数据。怎么写到option 里面呢?是直接操作 forceModel.ecModel.option.series 吗?

另外一个问题是,当x y 位置初始化好了后(stopped==true)有哪个event / callback 能触发?我想的是最初用 force,但是当初始化好了后,改成 none,提供 x, y 坐标,这样我可以保存位置数据,下次load 的时候就是上次结束的位置。

谢谢!

github-actions[bot] commented 2 years ago

This issue has been automatically marked as stale because it did not have recent activity. It will be closed in 7 days if no further activity occurs. If you wish not to mark it as stale, please leave a comment in this issue.

github-actions[bot] commented 2 years ago

This issue has been automatically closed because it did not have recent activity. If this remains to be a problem with the latest version of Apache ECharts, please open a new issue and link this to it. Thanks!