以下json数据的简短数组是此问题所基于的数据集。请注意,pink
仅出现在imports数组中,而不是“name”键的值。由于这个事实,我得到了这个错误
Uncaught TypeError: Cannot read property 'target' of undefined
尝试根据d3的创建者
创建基于this demo的蜂巢图var dataset = [
{"name":"red","imports":["blue, green"]},
{"name":"blue","imports":["yellow"]},
{"name":"green","imports":["blue", "pink"]},
{"name":"yellow","imports":["red"]}
];
问题:有没有办法用上面的异常数据点创建一个蜂巢图,或者,如果没有,还有什么是另一种使用的图形?
jsfiddle:jsfiddle (doesn't work)
代码
var width = 960,
height = 850,
innerRadius = 40,
outerRadius = 640,
majorAngle = 2 * Math.PI / 3,
minorAngle = 1 * Math.PI / 12;
var angle = d3.scale.ordinal()
.domain(["source", "source-target", "target-source", "target"])
.range([0, majorAngle - minorAngle, majorAngle + minorAngle, 2 * majorAngle]);
var radius = d3.scale.linear()
.range([innerRadius, outerRadius]);
var color = d3.scale.category10();
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + outerRadius * .20 + "," + outerRadius * .57 + ")");
// Load the data and display the plot!
d3.json("./fl.json", function(nodes) {
// d3.json("./flare-imports.json", function(nodes) {
console.log(nodes, "nodes");
var nodesByName = {},
links = [],
formatNumber = d3.format(",d"),
defaultInfo;
// Construct an index by node name.
nodes.forEach(function(d) {
d.connectors = [];
d.packageName = d.name.split(".")[1];
nodesByName[d.name] = d;
// console.log(d, nodesByName, "in nodes.forEach line 72, making connectors and packageName");
});
// Convert the import lists into links with sources and targets.
nodes.forEach(function(source) {
// console.log(source, "source?");
source.imports.forEach(function(targetName) {
var target = nodesByName[targetName];
// console.log(target, nodesByName, targetName, "looking for target");
if (!source.source) source.connectors.push(source.source = {node: source, degree: 0});
if (!target.target) target.connectors.push(target.target = {node: target, degree: 0});
links.push({source: source.source, target: target.target});
});
});
// Determine the type of each node, based on incoming and outgoing links.
nodes.forEach(function(node) {
if (node.source && node.target) {
node.type = node.source.type = "target-source";
node.target.type = "source-target";
} else if (node.source) {
node.type = node.source.type = "source";
} else if (node.target) {
node.type = node.target.type = "target";
} else {
node.connectors = [{node: node}];
node.type = "source";
}
// console.log(node, "checking node type");
});
// Initialize the info display.
var info = d3.select("#info")
.text(defaultInfo = "Showing " + formatNumber(links.length) + " dependencies among " + formatNumber(nodes.length) + " classes.");
// Normally, Hive Plots sort nodes by degree along each axis. However, since
// this example visualizes a package hierarchy, we get more interesting
// results if we group nodes by package. We don't need to sort explicitly
// because the data file is already sorted by class name.
// Nest nodes by type, for computing the rank.
var nodesByType = d3.nest()
.key(function(d) { return d.type; })
.sortKeys(d3.ascending)
.entries(nodes);
// Duplicate the target-source axis as source-target.
console.log(nodesByType, "nodes by type");
// nodesByType.push({key: "source-target", values: nodesByType[2].values});
nodesByType.push({key: "source-target", values: nodesByType[1].values});
// Compute the rank for each type, with padding between packages.
nodesByType.forEach(function(type) {
var lastName = type.values[0].packageName, count = 0;
type.values.forEach(function(d, i) {
if (d.packageName != lastName) lastName = d.packageName, count += 2;
d.index = count++;
});
type.count = count - 1;
});
// Set the radius domain.
radius.domain(d3.extent(nodes, function(d) { return d.index; }));
// Draw the axes.
svg.selectAll(".axis")
.data(nodesByType)
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d.key)) + ")"; })
.attr("x1", radius(-2))
.attr("x2", function(d) { return radius(d.count + 2); });
// Draw the links.
svg.append("g")
.attr("class", "links")
.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", link()
.angle(function(d) { return angle(d.type); })
.radius(function(d) { return radius(d.node.index); }))
.on("mouseover", linkMouseover)
.on("mouseout", mouseout);
// Draw the nodes. Note that each node can have up to two connectors,
// representing the source (outgoing) and target (incoming) links.
svg.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.style("fill", function(d) { return color(d.packageName); })
.selectAll("circle")
.data(function(d) { return d.connectors; })
.enter().append("circle")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d.type)) + ")"; })
.attr("cx", function(d) { return radius(d.node.index); })
.attr("r", 4)
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
// Highlight the link and connected nodes on mouseover.
function linkMouseover(d) {
svg.selectAll(".link").classed("active", function(p) { return p === d; });
svg.selectAll(".node circle").classed("active", function(p) { return p === d.source || p === d.target; });
info.text(d.source.node.name + " → " + d.target.node.name);
}
// Highlight the node and connected links on mouseover.
function nodeMouseover(d) {
svg.selectAll(".link").classed("active", function(p) { return p.source === d || p.target === d; });
d3.select(this).classed("active", true);
info.text(d.node.name);
}
// Clear any highlighted nodes or links.
function mouseout() {
svg.selectAll(".active").classed("active", false);
info.text(defaultInfo);
}
});
// A shape generator for Hive links, based on a source and a target.
// The source and target are defined in polar coordinates (angle and radius).
// Ratio links can also be drawn by using a startRadius and endRadius.
// This class is modeled after d3.svg.chord.
function link() {
var source = function(d) { return d.source; },
target = function(d) { return d.target; },
angle = function(d) { return d.angle; },
startRadius = function(d) { return d.radius; },
endRadius = startRadius,
arcOffset = -Math.PI / 2;
function link(d, i) {
var s = node(source, this, d, i),
t = node(target, this, d, i),
x;
if (t.a < s.a) x = t, t = s, s = x;
if (t.a - s.a > Math.PI) s.a += 2 * Math.PI;
var a1 = s.a + (t.a - s.a) / 3,
a2 = t.a - (t.a - s.a) / 3;
return s.r0 - s.r1 || t.r0 - t.r1
? "M" + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0
+ "L" + Math.cos(s.a) * s.r1 + "," + Math.sin(s.a) * s.r1
+ "C" + Math.cos(a1) * s.r1 + "," + Math.sin(a1) * s.r1
+ " " + Math.cos(a2) * t.r1 + "," + Math.sin(a2) * t.r1
+ " " + Math.cos(t.a) * t.r1 + "," + Math.sin(t.a) * t.r1
+ "L" + Math.cos(t.a) * t.r0 + "," + Math.sin(t.a) * t.r0
+ "C" + Math.cos(a2) * t.r0 + "," + Math.sin(a2) * t.r0
+ " " + Math.cos(a1) * s.r0 + "," + Math.sin(a1) * s.r0
+ " " + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0
: "M" + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0
+ "C" + Math.cos(a1) * s.r1 + "," + Math.sin(a1) * s.r1
+ " " + Math.cos(a2) * t.r1 + "," + Math.sin(a2) * t.r1
+ " " + Math.cos(t.a) * t.r1 + "," + Math.sin(t.a) * t.r1;
}
function node(method, thiz, d, i) {
var node = method.call(thiz, d, i),
a = +(typeof angle === "function" ? angle.call(thiz, node, i) : angle) + arcOffset,
r0 = +(typeof startRadius === "function" ? startRadius.call(thiz, node, i) : startRadius),
r1 = (startRadius === endRadius ? r0 : +(typeof endRadius === "function" ? endRadius.call(thiz, node, i) : endRadius));
return {r0: r0, r1: r1, a: a};
}
link.source = function(_) {
if (!arguments.length) return source;
source = _;
return link;
};
link.target = function(_) {
if (!arguments.length) return target;
target = _;
return link;
};
link.angle = function(_) {
if (!arguments.length) return angle;
angle = _;
return link;
};
link.radius = function(_) {
if (!arguments.length) return startRadius;
startRadius = endRadius = _;
return link;
};
link.startRadius = function(_) {
if (!arguments.length) return startRadius;
startRadius = _;
return link;
};
link.endRadius = function(_) {
if (!arguments.length) return endRadius;
endRadius = _;
return link;
};
return link;
}
function degrees(radians) {
return radians / Math.PI * 180 - 90;
}