在呈现 D3.js 图之前从 GET 请求获取数据
Get the data from a GET request before rendering D3.js graph
我正在尝试制作数据库中一些数据的网络图,我正在为前端开发 AngularJS(后端为 C#)。
我找到了 d3.js,它看起来很完美,但我根本不是 JavaScript 方面的专家。
我从他那里得到了例子 https://observablehq.com/@d3/mobile-patent-suits?collection=@d3/d3-force
(实际上它必须是以前的版本或者有人从 Github 中分叉出来的东西,我已经找不到了,但那是核心)。
我的问题是,我必须从调用 API 和 return 的函数 self.GetLinks
加载 JSON var links
数组 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;
}
}
你需要做两件事。第一个是将逻辑分为“首次渲染”和“更新”部分。在第一个渲染部分,您只是假设您正在绘制一个空白的 SVG。在更新部分,您使用您的数据更新已更改的内容。
在这个例子中,第一个渲染部分可以用来绘制svg
和defs
。然后更新部分可以处理在正确位置绘制节点和链接。
您需要做的第二件事是查看触发函数。这可以在您现在使用的回调中或通过承诺来完成(参见 AngularJS' $q
)。
最后,不在函数或构造函数中执行所有这些初始化操作是不好的做法。我已将您的代码拆分为 firstRender
和 update
部分,从 $onInit
调用 firstRender
,并仅将 API 模拟为 return 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>
我正在尝试制作数据库中一些数据的网络图,我正在为前端开发 AngularJS(后端为 C#)。
我找到了 d3.js,它看起来很完美,但我根本不是 JavaScript 方面的专家。 我从他那里得到了例子 https://observablehq.com/@d3/mobile-patent-suits?collection=@d3/d3-force (实际上它必须是以前的版本或者有人从 Github 中分叉出来的东西,我已经找不到了,但那是核心)。
我的问题是,我必须从调用 API 和 return 的函数 self.GetLinks
加载 JSON var links
数组 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;
}
}
你需要做两件事。第一个是将逻辑分为“首次渲染”和“更新”部分。在第一个渲染部分,您只是假设您正在绘制一个空白的 SVG。在更新部分,您使用您的数据更新已更改的内容。
在这个例子中,第一个渲染部分可以用来绘制svg
和defs
。然后更新部分可以处理在正确位置绘制节点和链接。
您需要做的第二件事是查看触发函数。这可以在您现在使用的回调中或通过承诺来完成(参见 AngularJS' $q
)。
最后,不在函数或构造函数中执行所有这些初始化操作是不好的做法。我已将您的代码拆分为 firstRender
和 update
部分,从 $onInit
调用 firstRender
,并仅将 API 模拟为 return 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>