呈现D3.js图之前,从GET请求中获取数据

时间:2020-10-17 16:36:08

标签: javascript angularjs svg d3.js

我正在尝试对数据库中的某些数据进行网络图绘制,我正在前端使用AngularJS(后端使用C#)。

我找到了d3.js,它看起来很完美,但我根本不是JavaScript专家。 我以他为例https://observablehq.com/@d3/mobile-patent-suits?collection=@d3/d3-force (实际上,它必须是以前的版本,或者是有人从Github分叉的东西,我再也找不到了,但这仍然是核心)。

我的问题是,我必须从调用API的函数var links加载JSON self.GetLinks数组,并返回相同类型对象的JSON。 该函数在onInit中被调用,但是到被调用时,所有内容都已经呈现在屏幕上了,self.GetLinks的唯一结果就是改变了var links内部的内容迟了。

为清楚起见,这里显示的数组var links是我从示例中获取的数组,对我来说没有用,但结构相同。

我需要了解在发生任何事情之前如何调用self.GetLinks,以便var links的内容是我实际上从后端获得的内容,或者甚至更好地如何使它{ var links的内容发生变化,将再次呈现图形(通过这种方式,我可以向其中动态添加元素)。

function MyController($scope, MyService, $routeParams, viewModelHelper) {

    var self = this;
    self.model = {
        id: parseInt($routeParams.Id) || -1,
        links: {},
    };

    self.$onInit = function () {
        self.GetLinks(self.model.id);
    };

     var links = [
        { source: "Microsoft", target: "Amazon", type: "licensing" },
        { source: "Microsoft", target: "HTC", type: "licensing" },
        { source: "Samsung", target: "Apple", type: "suit" },
        { source: "Motorola", target: "Apple", type: "suit" },
        { source: "Nokia", target: "Apple", type: "resolved" },
        { source: "HTC", target: "Apple", type: "suit" },
        { source: "Kodak", target: "Apple", type: "suit" },
        { source: "Microsoft", target: "Barnes & Noble", type: "suit" },
        { source: "Microsoft", target: "Foxconn", type: "suit" },
        { source: "Oracle", target: "Google", type: "suit" },
        { source: "Apple", target: "HTC", type: "suit" },
        { source: "Microsoft", target: "Inventec", type: "suit" },
        { source: "Samsung", target: "Kodak", type: "resolved" },
        { source: "LG", target: "Kodak", type: "resolved" },
        { source: "RIM", target: "Kodak", type: "suit" },
        { source: "Sony", target: "LG", type: "suit" },
        { source: "Kodak", target: "LG", type: "resolved" },
        { source: "Apple", target: "Nokia", type: "resolved" },
        { source: "Qualcomm", target: "Nokia", type: "resolved" },
        { source: "Apple", target: "Motorola", type: "suit" },
        { source: "Microsoft", target: "Motorola", type: "suit" },
        { source: "Motorola", target: "Microsoft", type: "suit" },
        { source: "Huawei", target: "ZTE", type: "suit" },
        { source: "Ericsson", target: "ZTE", type: "suit" },
        { source: "Kodak", target: "Samsung", type: "resolved" },
        { source: "Apple", target: "Samsung", type: "suit" },
        { source: "Kodak", target: "RIM", type: "suit" },
        { source: "Nokia", target: "Qualcomm", type: "suit" },
        { source: "Pippo", target: "Pippo" },
        { source: "Paperino", target: "Pippo", type: "suit" }
    ];

    self.GetLinks = function (id) {
        viewModelHelper.apiGet("Api/GetLinks/" + id,
            null,
            function (result) {
                links = result.data.Elements;

                var message = "Success";
                setResultMessage(message, "Info");
            },
            function (result) {

                var message = "Error " + result.data.Message;
                setResultMessage(message, "danger");
            },
            function (result) {

            });        

    };

    var nodes = {};


    // Compute the distinct nodes from the links.
    links.forEach(function (link) {
        link.source = nodes[link.source] || (nodes[link.source] = { name: link.source });
        link.target = nodes[link.target] || (nodes[link.target] = { name: link.target });
    });


    var w = 1400,
        h = 900;

    var force = d3.layout.force()
        .nodes(d3.values(nodes))
        .links(links)
        .size([w, h])
        .linkDistance(200)
        .charge(-1200)
        .on("tick", tick)
        .start();

    var svg = d3.select("body").append("svg:svg")
        .attr("width", w)
        .attr("height", h);

    // Per-type markers, as they don't inherit styles.
    svg.append("svg:defs").selectAll("marker")
        .data(["suit", "licensing", "resolved"])
        .enter().append("svg:marker")
        .attr("id", String)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 15)
        .attr("refY", -1.5)
        .attr("markerWidth", 6)
        .attr("markerHeight", 6)
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5");

    var path = svg.append("svg:g").selectAll("path")
        .data(force.links())
        .enter().append("svg:path")
        .attr("id", function (d) { return d.source.index + "_" + d.target.index; })
        .attr("class", function (d) { return "link " + d.type; })
        .attr("marker-end", function (d) { return "url(#" + d.type + ")"; });

    var circle = svg.append("svg:g").selectAll("circle")
        .data(force.nodes())
        .enter().append("svg:image")
        .attr("class", "circle")
        .attr("xlink:href", "/Image/icon.png")
        .attr("x", "-8px")
        .attr("y", "-8px")
        .attr("width", "50px")
        .attr("height", "50px")
        .call(force.drag);



    var text = svg.append("svg:g").selectAll("g")
        .data(force.nodes())
        .enter().append("svg:g");

    // A copy of the text with a thick white stroke for legibility.
    text.append("svg:text")
        .attr("x", 2)
        .attr("y", 50)//".31em"
        .attr("class", "shadow")
        .text(function (d) { return d.name; });

    text.append("svg:text")
        .attr("x", 2)
        .attr("y", 50)
        .text(function (d) { return d.name; });

    var path_label = svg.append("svg:g").selectAll(".path_label")
        .data(force.links())
        .enter().append("svg:text")
        .attr("class", "path_label")
        .append("svg:textPath")
        .attr("startOffset", "50%")
        .attr("text-anchor", "middle")
        .attr("xlink:href", function (d) { return "#" + d.source.index + "_" + d.target.index; })
        .style("fill", "#000")
        .style("font-family", "Arial")
        .text(function (d) { return d.type; });

    // Use elliptical arc path segments to doubly-encode directionality.
    function tick() {
        path.attr("d", function (d) {
            var dx = d.target.x - d.source.x,
                dy = d.target.y - d.source.y,
                dr = Math.sqrt(dx * dx + dy * dy);
            return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
        });

        circle.attr("transform", function (d) {
            return "translate(" + d.x + "," + d.y + ")";
        });

        text.attr("transform", function (d) {
            return "translate(" + d.x + "," + d.y + ")";
        });
    }

    function setResultMessage(message, type) {
        self.ResponseModel = {};
        self.ResponseModel.ResponseAlert = true;
        self.ResponseModel.ResponseType = type;
        self.ResponseModel.ResponseMessage = message;
    }
}

1 个答案:

答案 0 :(得分:0)

您需要做两件事。首先将逻辑分为“第一渲染”和“更新”部分。在第一个渲染部分中,您仅假设要在空白的SVG上绘制。在更新部分,您将使用数据更新已更改的内容。

在此示例中,第一个渲染部分可用于绘制svgdefs。然后更新部分可以处理在正确位置绘制节点和链接的情况。


您需要做的第二件事是查看触发功能。可以在您现在使用的回调中或通过Promise完成(有关此信息,请参见AngularJS的$q)。


最后,在函数或构造函数中进行所有这些初始化工作是不好的做法。我已将您的代码分为firstRenderupdate部分,从firstRender调用$onInit,并模拟了API仅在2秒后返回。请注意,在这2秒钟后,图表将如何更新。

function MyController($scope) {

  var self = this;
  self.model = {
    id: -1,
    links: {},
  };

  self.$onInit = function() {
    self.GetLinks(self.model.id);
    self.firstRender();
  };

  var links = [];
  var nodes = {};
  var w = 1400,
    h = 900;
  var force;
  var svg;
  var paths;
  var circles;
  var texts;
  var pathLabels;

  self.GetLinks = function(id) {
    viewModelHelper.apiGet("Api/GetLinks/" + id,
      null,
      function(result) {
        links = result;
        self.update();
      }
    )
  };

  self.firstRender = function() {
    force = d3.layout.force()
      .size([w, h])
      .linkDistance(200)
      .charge(-1200)
      .on("tick", tick);

    svg = d3.select("body").append("svg:svg")
      .attr("width", w)
      .attr("height", h);

    // Per-type markers, as they don't inherit styles.
    svg.append("svg:defs").selectAll("marker")
      .data(["suit", "licensing", "resolved"])
      .enter().append("svg:marker")
      .attr("id", String)
      .attr("viewBox", "0 -5 10 10")
      .attr("refX", 15)
      .attr("refY", -1.5)
      .attr("markerWidth", 6)
      .attr("markerHeight", 6)
      .attr("orient", "auto")
      .append("svg:path")
      .attr("d", "M0,-5L10,0L0,5");

    paths = svg.append("svg:g");
    circles = svg.append("svg:g");
    texts = svg.append("svg:g");
    pathLabels = svg.append("svg:g");
  }

  self.update = function() {
    // Compute the distinct nodes from the links.
    links.forEach(function(link) {
      link.source = nodes[link.source] || (nodes[link.source] = {
        name: link.source
      });
      link.target = nodes[link.target] || (nodes[link.target] = {
        name: link.target
      });
    });

    force
      .nodes(d3.values(nodes))
      .links(links)
      .start();

    var path = paths.selectAll("path")
      .data(force.links());

    path.exit()
      .remove();
    path.enter()
      .append("svg:path");

    path.attr("id", function(d) {
        return d.source.index + "_" + d.target.index;
      })
      .attr("class", function(d) {
        return "link " + d.type;
      })
      .attr("marker-end", function(d) {
        return "url(#" + d.type + ")";
      });

    // var circle = circles.selectAll(".circle")
    //   .data(force.nodes());
    //
    // circle.exit()
    //   .remove();
    // circle.enter()
    //   .append("svg:image")
    //   .attr("class", "circle")
    //   .attr("xlink:href", "/Image/icon.png")
    //   .attr("x", "-8px")
    //   .attr("y", "-8px")
    //   .attr("width", "50px")
    //   .attr("height", "50px")
    //   .call(force.drag);
    var circle = circles.selectAll(".circle")
      .data(force.nodes());

    circle.exit()
      .remove();
    circle.enter()
      .append("svg:circle")
      .attr("class", "circle")
      .attr("r", 16)
      .call(force.drag);

    var text = texts.selectAll("g")
      .data(force.nodes());

    text.exit()
      .remove();
    text.enter()
      .append("svg:text")
      .attr("x", 2)
      .attr("y", 50) //".31em"

    text.text(function(d) {
      return d.name;
    });

    var pathLabel = pathLabels
      .selectAll(".path_label")
      .data(force.links());

    pathLabel.exit().remove();
    pathLabel.enter()
      .append("svg:text")
      .attr("class", "path_label")
      .append("svg:textPath")
      .attr("startOffset", "50%")
      .attr("text-anchor", "middle")
      .style("fill", "#000")
      .style("font-family", "Arial");

    pathLabel
      .attr("xlink:href", function(d) {
        return "#" + d.source.index + "_" + d.target.index;
      })
      .text(function(d) {
        return d.type;
      });
  }

  // Use elliptical arc path segments to doubly-encode directionality.
  function tick() {
    paths.selectAll("path").attr("d", function(d) {
      var dx = d.target.x - d.source.x,
        dy = d.target.y - d.source.y,
        dr = Math.sqrt(dx * dx + dy * dy);
      return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
    });

    circles.selectAll("circle").attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });

    texts.selectAll("text").attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });
  }
}

angular.module("app", [])
  .controller("ctrl", MyController);

var viewModelHelper = {
  apiGet: function(a, b, then) {
    setTimeout(
      function() {
        console.log("Response from server");
        then(viewModelHelper.links);
      },
      2000
    );
  },
  links: [{
      source: "Microsoft",
      target: "Amazon",
      type: "licensing"
    },
    {
      source: "Microsoft",
      target: "HTC",
      type: "licensing"
    },
    {
      source: "Samsung",
      target: "Apple",
      type: "suit"
    },
    {
      source: "Motorola",
      target: "Apple",
      type: "suit"
    },
    {
      source: "Nokia",
      target: "Apple",
      type: "resolved"
    },
    {
      source: "HTC",
      target: "Apple",
      type: "suit"
    },
    {
      source: "Kodak",
      target: "Apple",
      type: "suit"
    },
    {
      source: "Microsoft",
      target: "Barnes & Noble",
      type: "suit"
    },
    {
      source: "Microsoft",
      target: "Foxconn",
      type: "suit"
    },
    {
      source: "Oracle",
      target: "Google",
      type: "suit"
    },
    {
      source: "Apple",
      target: "HTC",
      type: "suit"
    },
    {
      source: "Microsoft",
      target: "Inventec",
      type: "suit"
    },
    {
      source: "Samsung",
      target: "Kodak",
      type: "resolved"
    },
    {
      source: "LG",
      target: "Kodak",
      type: "resolved"
    },
    {
      source: "RIM",
      target: "Kodak",
      type: "suit"
    },
    {
      source: "Sony",
      target: "LG",
      type: "suit"
    },
    {
      source: "Kodak",
      target: "LG",
      type: "resolved"
    },
    {
      source: "Apple",
      target: "Nokia",
      type: "resolved"
    },
    {
      source: "Qualcomm",
      target: "Nokia",
      type: "resolved"
    },
    {
      source: "Apple",
      target: "Motorola",
      type: "suit"
    },
    {
      source: "Microsoft",
      target: "Motorola",
      type: "suit"
    },
    {
      source: "Motorola",
      target: "Microsoft",
      type: "suit"
    },
    {
      source: "Huawei",
      target: "ZTE",
      type: "suit"
    },
    {
      source: "Ericsson",
      target: "ZTE",
      type: "suit"
    },
    {
      source: "Kodak",
      target: "Samsung",
      type: "resolved"
    },
    {
      source: "Apple",
      target: "Samsung",
      type: "suit"
    },
    {
      source: "Kodak",
      target: "RIM",
      type: "suit"
    },
    {
      source: "Nokia",
      target: "Qualcomm",
      type: "suit"
    },
    {
      source: "Pippo",
      target: "Pippo"
    },
    {
      source: "Paperino",
      target: "Pippo",
      type: "suit"
    }
  ],
};
path {
  fill: none;
  stroke: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<div ng-app="app" ng-controller="ctrl">
</div>