I am currently exploring a large proprietary code base with many individual projects and interdependencies. To get a better overview, I would like to create a graphical representation of the dependencies, which are represented by a directed acyclic graph. The hope is, to finally better understand dependency clusters and (with knowledge about the individual project's contents) to aid restructuring of projects and dependencies.
Currently, I am using D3 for the visualization. Below is the full code of my current attempt (with project names replaced by a dummy). As you can see, it still looks pretty messy. Some order can be seen when hovering over project nodes.
I already looked at (but did not try out, yet) d3-dag, which might be promising but only contains examples with rather small data sets.
I also thought about something like a graph using hierarchical edge bundling, but there is not really a hierarchy here.
Does anybody have a recommendation for a better approach than the currently used force simulation layout?
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<style>
#controls {
position: fixed;
left: 0px;
width: 20%;
top: 0px;
height: 10%;
}
#chart {
position: fixed;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
}
path.link {
fill: none;
stroke: #c5c5c5;
stroke-width: 1.0px;
}
circle {
fill: #ccc;
stroke: #fff;
stroke-width: 1.5px;
}
text {
fill: #000000;
font: 11px sans-serif;
pointer-events: none;
}
.ingoing {
stroke: #237690!important;
stroke-width: 1.5px!important;
}
.outgoing {
stroke: #FA1209!important;
stroke-width: 1.5px!important;
}
.selected {
stroke: #000000!important;
stroke-width: 1.5px!important;
}
</style>
<body>
<div id="chart"></div>
<div id="controls">
<input type="checkbox" id="cb_hierarchical" checked="True" onclick='redraw();'>Order by dependency chain<br>
<input type="checkbox" id="cb_curved" checked="True" onclick='redraw();'>Curved Lines<br>
</div>
<script>
graph = JSON.parse('{"directed": true, "graph": [], "nodes": [{"id": 0, "name": "Project", "level": 7}, {"id": 1, "name": "Project", "level": 2}, {"id": 2, "name": "Project", "level": 12}, {"id": 3, "name": "Project", "level": 12}, {"id": 4, "name": "Project", "level": 3}, {"id": 5, "name": "Project", "level": 3}, {"id": 6, "name": "Project", "level": 8}, {"id": 7, "name": "Project", "level": 9}, {"id": 8, "name": "Project", "level": 1}, {"id": 9, "name": "Project", "level": 5}, {"id": 10, "name": "Project", "level": 12}, {"id": 11, "name": "Project", "level": 8}, {"id": 12, "name": "Project", "level": 10}, {"id": 13, "name": "Project", "level": 4}, {"id": 14, "name": "Project", "level": 3}, {"id": 15, "name": "Project", "level": 6}, {"id": 16, "name": "Project", "level": 7}, {"id": 17, "name": "Project", "level": 3}, {"id": 18, "name": "Project", "level": 9}, {"id": 19, "name": "Project", "level": 9}, {"id": 20, "name": "Project", "level": 7}, {"id": 21, "name": "Project", "level": 13}, {"id": 22, "name": "Project", "level": 7}, {"id": 23, "name": "Project", "level": 8}, {"id": 24, "name": "Project", "level": 3}, {"id": 25, "name": "Project", "level": 0}, {"id": 26, "name": "Project", "level": 6}, {"id": 27, "name": "Project", "level": 7}, {"id": 28, "name": "Project", "level": 15}, {"id": 29, "name": "Project", "level": 8}, {"id": 30, "name": "Project", "level": 9}, {"id": 31, "name": "Project", "level": 3}, {"id": 32, "name": "Project", "level": 8}, {"id": 33, "name": "Project", "level": 3}, {"id": 34, "name": "Project", "level": 2}, {"id": 35, "name": "Project", "level": 12}, {"id": 36, "name": "Project", "level": 8}, {"id": 37, "name": "Project", "level": 6}, {"id": 38, "name": "Project", "level": 8}, {"id": 39, "name": "Project", "level": 8}, {"id": 40, "name": "Project", "level": 6}, {"id": 41, "name": "Project", "level": 8}, {"id": 42, "name": "Project", "level": 8}, {"id": 43, "name": "Project", "level": 5}, {"id": 44, "name": "Project", "level": 15}, {"id": 45, "name": "Project", "level": 1}, {"id": 46, "name": "Project", "level": 12}, {"id": 47, "name": "Project", "level": 8}, {"id": 48, "name": "Project", "level": 7}, {"id": 49, "name": "Project", "level": 1}, {"id": 50, "name": "Project", "level": 14}, {"id": 51, "name": "Project", "level": 1}, {"id": 52, "name": "Project", "level": 6}, {"id": 53, "name": "Project", "level": 15}, {"id": 54, "name": "Project", "level": 15}, {"id": 55, "name": "Project", "level": 9}, {"id": 56, "name": "Project", "level": 4}, {"id": 57, "name": "Project", "level": 8}, {"id": 58, "name": "Project", "level": 1}, {"id": 59, "name": "Project", "level": 1}, {"id": 60, "name": "Project", "level": 15}, {"id": 61, "name": "Project", "level": 7}, {"id": 62, "name": "Project", "level": 8}, {"id": 63, "name": "Project", "level": 7}, {"id": 64, "name": "Project", "level": 3}, {"id": 65, "name": "Project", "level": 7}, {"id": 66, "name": "Project", "level": 1}, {"id": 67, "name": "Project", "level": 14}, {"id": 68, "name": "Project", "level": 5}, {"id": 69, "name": "Project", "level": 8}, {"id": 70, "name": "Project", "level": 7}, {"id": 71, "name": "Project", "level": 7}, {"id": 72, "name": "Project", "level": 5}, {"id": 73, "name": "Project", "level": 6}, {"id": 74, "name": "Project", "level": 2}, {"id": 75, "name": "Project", "level": 7}, {"id": 76, "name": "Project", "level": 4}, {"id": 77, "name": "Project", "level": 3}, {"id": 78, "name": "Project", "level": 8}, {"id": 79, "name": "Project", "level": 4}, {"id": 80, "name": "Project", "level": 3}, {"id": 81, "name": "Project", "level": 2}, {"id": 82, "name": "Project", "level": 16}, {"id": 83, "name": "Project", "level": 13}, {"id": 84, "name": "Project", "level": 12}, {"id": 85, "name": "Project", "level": 11}, {"id": 86, "name": "Project", "level": 3}, {"id": 87, "name": "Project", "level": 9}, {"id": 88, "name": "Project", "level": 2}, {"id": 89, "name": "Project", "level": 6}, {"id": 90, "name": "Project", "level": 5}, {"id": 91, "name": "Project", "level": 3}, {"id": 92, "name": "Project", "level": 5}, {"id": 93, "name": "Project", "level": 5}, {"id": 94, "name": "Project", "level": 4}, {"id": 95, "name": "Project", "level": 1}, {"id": 96, "name": "Project", "level": 13}, {"id": 97, "name": "Project", "level": 8}, {"id": 98, "name": "Project", "level": 5}, {"id": 99, "name": "Project", "level": 4}, {"id": 100, "name": "Project", "level": 6}, {"id": 101, "name": "Project", "level": 8}, {"id": 102, "name": "Project", "level": 11}, {"id": 103, "name": "Project", "level": 5}, {"id": 104, "name": "Project", "level": 10}], "links": [{"source": 0, "target": 57}, {"source": 0, "target": 36}, {"source": 0, "target": 23}, {"source": 0, "target": 82}, {"source": 0, "target": 39}, {"source": 0, "target": 18}, {"source": 0, "target": 41}, {"source": 0, "target": 6}, {"source": 0, "target": 85}, {"source": 0, "target": 60}, {"source": 1, "target": 50}, {"source": 1, "target": 4}, {"source": 1, "target": 77}, {"source": 1, "target": 85}, {"source": 1, "target": 75}, {"source": 1, "target": 14}, {"source": 2, "target": 50}, {"source": 2, "target": 83}, {"source": 2, "target": 67}, {"source": 3, "target": 50}, {"source": 3, "target": 67}, {"source": 3, "target": 21}, {"source": 4, "target": 50}, {"source": 4, "target": 7}, {"source": 4, "target": 20}, {"source": 5, "target": 75}, {"source": 5, "target": 60}, {"source": 5, "target": 85}, {"source": 6, "target": 104}, {"source": 7, "target": 50}, {"source": 7, "target": 12}, {"source": 7, "target": 85}, {"source": 8, "target": 50}, {"source": 8, "target": 35}, {"source": 8, "target": 91}, {"source": 8, "target": 96}, {"source": 8, "target": 26}, {"source": 8, "target": 68}, {"source": 9, "target": 101}, {"source": 9, "target": 7}, {"source": 9, "target": 62}, {"source": 12, "target": 85}, {"source": 12, "target": 102}, {"source": 13, "target": 22}, {"source": 13, "target": 52}, {"source": 13, "target": 27}, {"source": 13, "target": 47}, {"source": 13, "target": 30}, {"source": 13, "target": 43}, {"source": 13, "target": 60}, {"source": 13, "target": 61}, {"source": 13, "target": 62}, {"source": 13, "target": 92}, {"source": 13, "target": 7}, {"source": 13, "target": 9}, {"source": 13, "target": 70}, {"source": 13, "target": 68}, {"source": 13, "target": 12}, {"source": 13, "target": 37}, {"source": 13, "target": 73}, {"source": 13, "target": 15}, {"source": 13, "target": 16}, {"source": 13, "target": 100}, {"source": 13, "target": 101}, {"source": 13, "target": 20}, {"source": 13, "target": 103}, {"source": 14, "target": 75}, {"source": 14, "target": 50}, {"source": 14, "target": 85}, {"source": 14, "target": 56}, {"source": 14, "target": 98}, {"source": 15, "target": 70}, {"source": 15, "target": 22}, {"source": 15, "target": 7}, {"source": 16, "target": 85}, {"source": 16, "target": 101}, {"source": 17, "target": 50}, {"source": 17, "target": 85}, {"source": 18, "target": 35}, {"source": 25, "target": 30}, {"source": 25, "target": 51}, {"source": 25, "target": 80}, {"source": 25, "target": 4}, {"source": 25, "target": 95}, {"source": 25, "target": 13}, {"source": 25, "target": 59}, {"source": 25, "target": 77}, {"source": 25, "target": 1}, {"source": 25, "target": 75}, {"source": 25, "target": 8}, {"source": 25, "target": 64}, {"source": 25, "target": 14}, {"source": 25, "target": 49}, {"source": 25, "target": 33}, {"source": 25, "target": 58}, {"source": 25, "target": 66}, {"source": 25, "target": 45}, {"source": 20, "target": 85}, {"source": 20, "target": 7}, {"source": 21, "target": 67}, {"source": 22, "target": 85}, {"source": 24, "target": 50}, {"source": 24, "target": 85}, {"source": 26, "target": 30}, {"source": 26, "target": 50}, {"source": 26, "target": 35}, {"source": 26, "target": 0}, {"source": 26, "target": 63}, {"source": 26, "target": 48}, {"source": 26, "target": 75}, {"source": 26, "target": 65}, {"source": 27, "target": 101}, {"source": 29, "target": 50}, {"source": 29, "target": 60}, {"source": 29, "target": 87}, {"source": 29, "target": 19}, {"source": 30, "target": 60}, {"source": 30, "target": 44}, {"source": 30, "target": 85}, {"source": 30, "target": 21}, {"source": 30, "target": 53}, {"source": 32, "target": 50}, {"source": 32, "target": 60}, {"source": 32, "target": 18}, {"source": 32, "target": 19}, {"source": 33, "target": 99}, {"source": 33, "target": 48}, {"source": 33, "target": 26}, {"source": 33, "target": 75}, {"source": 33, "target": 98}, {"source": 33, "target": 68}, {"source": 33, "target": 79}, {"source": 34, "target": 75}, {"source": 34, "target": 17}, {"source": 34, "target": 33}, {"source": 37, "target": 27}, {"source": 37, "target": 70}, {"source": 37, "target": 101}, {"source": 38, "target": 67}, {"source": 38, "target": 96}, {"source": 40, "target": 67}, {"source": 41, "target": 82}, {"source": 66, "target": 75}, {"source": 66, "target": 74}, {"source": 66, "target": 88}, {"source": 45, "target": 81}, {"source": 47, "target": 30}, {"source": 47, "target": 67}, {"source": 47, "target": 46}, {"source": 47, "target": 96}, {"source": 48, "target": 32}, {"source": 48, "target": 18}, {"source": 48, "target": 29}, {"source": 49, "target": 50}, {"source": 49, "target": 60}, {"source": 49, "target": 85}, {"source": 49, "target": 5}, {"source": 50, "target": 60}, {"source": 50, "target": 44}, {"source": 50, "target": 54}, {"source": 50, "target": 53}, {"source": 51, "target": 75}, {"source": 51, "target": 65}, {"source": 51, "target": 34}, {"source": 51, "target": 33}, {"source": 52, "target": 7}, {"source": 52, "target": 101}, {"source": 56, "target": 75}, {"source": 56, "target": 65}, {"source": 56, "target": 98}, {"source": 58, "target": 75}, {"source": 58, "target": 17}, {"source": 58, "target": 33}, {"source": 59, "target": 50}, {"source": 59, "target": 35}, {"source": 59, "target": 1}, {"source": 59, "target": 31}, {"source": 59, "target": 85}, {"source": 59, "target": 72}, {"source": 59, "target": 75}, {"source": 59, "target": 14}, {"source": 59, "target": 67}, {"source": 60, "target": 82}, {"source": 62, "target": 50}, {"source": 62, "target": 60}, {"source": 62, "target": 85}, {"source": 62, "target": 55}, {"source": 63, "target": 87}, {"source": 63, "target": 36}, {"source": 63, "target": 69}, {"source": 63, "target": 23}, {"source": 63, "target": 82}, {"source": 63, "target": 60}, {"source": 63, "target": 41}, {"source": 63, "target": 85}, {"source": 63, "target": 97}, {"source": 63, "target": 11}, {"source": 64, "target": 15}, {"source": 64, "target": 16}, {"source": 64, "target": 22}, {"source": 64, "target": 52}, {"source": 64, "target": 100}, {"source": 64, "target": 101}, {"source": 64, "target": 85}, {"source": 64, "target": 9}, {"source": 64, "target": 62}, {"source": 64, "target": 94}, {"source": 65, "target": 42}, {"source": 67, "target": 28}, {"source": 68, "target": 50}, {"source": 68, "target": 60}, {"source": 70, "target": 50}, {"source": 70, "target": 62}, {"source": 70, "target": 84}, {"source": 70, "target": 85}, {"source": 70, "target": 7}, {"source": 70, "target": 47}, {"source": 71, "target": 38}, {"source": 71, "target": 67}, {"source": 71, "target": 78}, {"source": 72, "target": 30}, {"source": 73, "target": 50}, {"source": 73, "target": 16}, {"source": 73, "target": 22}, {"source": 73, "target": 60}, {"source": 73, "target": 61}, {"source": 73, "target": 85}, {"source": 73, "target": 20}, {"source": 74, "target": 30}, {"source": 74, "target": 50}, {"source": 74, "target": 80}, {"source": 74, "target": 35}, {"source": 74, "target": 24}, {"source": 74, "target": 14}, {"source": 74, "target": 13}, {"source": 74, "target": 77}, {"source": 74, "target": 5}, {"source": 74, "target": 91}, {"source": 74, "target": 62}, {"source": 74, "target": 64}, {"source": 74, "target": 72}, {"source": 74, "target": 86}, {"source": 74, "target": 103}, {"source": 74, "target": 26}, {"source": 74, "target": 101}, {"source": 74, "target": 31}, {"source": 74, "target": 75}, {"source": 75, "target": 85}, {"source": 76, "target": 90}, {"source": 76, "target": 67}, {"source": 77, "target": 50}, {"source": 77, "target": 76}, {"source": 77, "target": 85}, {"source": 77, "target": 42}, {"source": 80, "target": 50}, {"source": 80, "target": 85}, {"source": 83, "target": 50}, {"source": 84, "target": 96}, {"source": 85, "target": 50}, {"source": 85, "target": 35}, {"source": 85, "target": 21}, {"source": 85, "target": 82}, {"source": 85, "target": 60}, {"source": 85, "target": 83}, {"source": 85, "target": 2}, {"source": 85, "target": 46}, {"source": 85, "target": 84}, {"source": 85, "target": 3}, {"source": 85, "target": 10}, {"source": 86, "target": 13}, {"source": 86, "target": 37}, {"source": 87, "target": 104}, {"source": 88, "target": 85}, {"source": 89, "target": 50}, {"source": 89, "target": 71}, {"source": 90, "target": 50}, {"source": 91, "target": 67}, {"source": 92, "target": 50}, {"source": 92, "target": 85}, {"source": 93, "target": 67}, {"source": 94, "target": 85}, {"source": 94, "target": 52}, {"source": 94, "target": 62}, {"source": 95, "target": 50}, {"source": 95, "target": 85}, {"source": 95, "target": 68}, {"source": 43, "target": 16}, {"source": 43, "target": 22}, {"source": 43, "target": 37}, {"source": 43, "target": 101}, {"source": 43, "target": 85}, {"source": 43, "target": 20}, {"source": 96, "target": 67}, {"source": 97, "target": 104}, {"source": 98, "target": 75}, {"source": 98, "target": 65}, {"source": 98, "target": 26}, {"source": 98, "target": 89}, {"source": 98, "target": 40}, {"source": 99, "target": 50}, {"source": 99, "target": 67}, {"source": 99, "target": 93}, {"source": 99, "target": 72}, {"source": 100, "target": 50}, {"source": 101, "target": 7}, {"source": 103, "target": 30}, {"source": 103, "target": 15}, {"source": 103, "target": 22}, {"source": 103, "target": 52}, {"source": 103, "target": 60}, {"source": 103, "target": 61}, {"source": 103, "target": 100}, {"source": 103, "target": 35}, {"source": 103, "target": 73}, {"source": 103, "target": 20}, {"source": 103, "target": 70}, {"source": 103, "target": 7}], "multigraph": false}')
var cb_hierarchical = document.getElementById("cb_hierarchical");
var cb_curved = document.getElementById("cb_curved");
var chartDiv = document.getElementById("chart");
var svg = d3.select(chartDiv).append("svg");
var linkedByIndex = {};
var num_links = {};
var num_links_incoming = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source + "," + d.target] = 1;
num_links[d.target] = (num_links[d.target] != undefined ? num_links[d.target] + 1 : 1)
num_links[d.source] = (num_links[d.source] != undefined ? num_links[d.source] + 1 : 1)
num_links_incoming[d.target] = (num_links_incoming[d.target] != undefined ? num_links_incoming[d.target] + 1 : 1)
d.distance = graph.nodes[d.target].level - graph.nodes[d.source].level;
});
const maxlevel = graph.nodes.reduce(function(currentValue, node) {
return Math.max(node.level, currentValue);
}, 0);
var maxlinks = Object.values(num_links).reduce(function(currentValue, entry) {
return Math.max(entry, currentValue);
}, 0);
var white_background = svg.append("svg:defs")
.append("filter")
.attr("x", 0)
.attr("y", 0)
.attr("width", 1)
.attr("height", 1)
.attr("id", "white_background");
white_background
.append("feFlood")
.attr("flood-color", "white");
white_background
.append("feComposite")
.attr("in", "SourceGraphic")
var simulation = d3.forceSimulation(graph.nodes)
.on('tick', tick);
// add the links
var path = svg.selectAll("path")
.data(graph.links)
.enter().append("svg:path")
.attr("class", "link");
// define the nodes
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.on("mouseover", highlight(true))
.on("mouseout", highlight(false))
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// add the node-circles
var circle = node.append("circle")
.attr("z", 0);
// add the text to nodes
var title = node.append("text")
.attr("x", 12)
.attr("z", 5)
.attr("dy", ".35em")
.attr("filter", "url(#white_background)")
.text(function(d) {
return d.name;
});
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function tick(e) {
var width = chartDiv.clientWidth;
var height = chartDiv.clientHeight;
node
.attr("cx", function(d) {
return d.x = Math.max(d.radius, Math.min(width - d.radius - 75, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(d.radius, Math.min(height - d.radius, d.y));
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
path.attr("d", function(d) {
if (cb_curved.checked) {
return "M" + d.source.x + "," + d.source.y + "C" + d.target.x + "," + d.source.y + "," + d.source.x + "," + d.target.y + "," + d.target.x + "," + d.target.y;
} else {
return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
}
});
}
function highlight(active) {
return function(d, i) {
path.classed("ingoing", function(link) {
return active && link.target === d;
});
path.classed("outgoing", function(link) {
return active && link.source === d;
});
node.classed("ingoing", function(node) {
return active && linkedByIndex[node.id + "," + d.id];
});
node.classed("outgoing", function(node) {
return active && linkedByIndex[d.id + "," + node.id];
});
node.classed("selected", function(node) {
return active && d.id === node.id;
});
circle.classed("ingoing", function(node) {
return active && linkedByIndex[node.id + "," + d.id];
});
circle.classed("outgoing", function(node) {
return active && linkedByIndex[d.id + "," + node.id];
});
circle.classed("selected", function(node) {
return active && d.id === node.id;
});
title.classed("ingoing", function(node) {
return active && linkedByIndex[node.id + "," + d.id];
});
title.classed("outgoing", function(node) {
return active && linkedByIndex[d.id + "," + node.id];
});
title.classed("selected", function(node) {
return active && d.id === node.id;
});
};
}
function redraw() {
var width = chartDiv.clientWidth;
var height = chartDiv.clientHeight;
svg.attr("width", width).attr("height", height);
const radius = 25;
const charge = 10;
const link_strength = .75;
graph.nodes.forEach(function(d) {
d.radius = radius * Math.atan((num_links[d.id] != undefined ? num_links[d.id] : 0) / maxlinks * 2 * Math.PI);
});
graph.nodes.forEach(function(d) {
d.radius_inc = radius * Math.atan((num_links_incoming[d.id] != undefined ? num_links_incoming[d.id] : 0) / maxlinks * 2 * Math.PI);
});
circle.attr('r', function(d) {
return d.radius;
});
var leveldist = width / (maxlevel + 1);
if (cb_hierarchical.checked) {
simulation
.force('x', d3.forceX()
.x(function(d) {
return (d.level + 0.5) * leveldist;
})
.strength(2.))
.force('y', d3.forceY()
.y(height / 2)
.strength(0.025));
} else {
simulation
.force('x', d3.forceX()
.x(width / 2)
.strength(0.05))
.force('y', d3.forceY()
.y(height / 2)
.strength(0.05));
}
simulation.force('link', d3.forceLink()
.links(graph.links)
.strength(function(d) {
return link_strength / d.target.radius_inc;
})
.distance(function(d) {
return d.distance * leveldist
}))
.force('charge', d3.forceManyBody()
.strength(function(d) {
return -charge * d.radius;
}))
.force('collision', d3.forceCollide()
.radius(function(d) {
return d.radius;
}))
.alphaTarget(0.3)
.restart();
}
redraw();
d3.select(window).on("resize", redraw)
</script>
</body>
</html>
Using the comments by #Coola, I extended my recent attempts with a Sankey diagram, added padding and zooming capabilities and the possibility to switch between the different visualization modes. Furthermore, I added some link drawing style, to the force simulation approach, that tries to resemble bundling of electrical wires to prevent the mess.
Posting this as an answer instead of editing the original question as the change is rather huge and contains significant progress. Hope, this is OK.
Still, things are a bit messy as you can see. Thus, I would still be happy to see more ideas.
Currently, I do have the feeling that the Hierarchical Edge bundling is best-suited for this problem. To be able to create such a graph, I introduced an artificial hierarchy indirectly via the shortest path between the main project and its dependants.
Hierarchical Edge Bundling
Force Layout
Sankey Diagram
Unfortunately, the full code is too large to be posted.
Use the networkX library, first create a networkX graph and then call the layout function, I used spectral_layout as I feel it is a better way to visualize DAGs. The layout function will return a dictionary of node names with x,y positions. I did this in python, converted the dictionary to json and passed over the json to D3.
I have a line chart with nvd3.
It works fine when I have fewer points, but I get a javascript error when I have more point:
Cannot read property 'x' of null
Here is the full html for the case with more data which fails:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.7.0/nv.d3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.7.0/nv.d3.min.js"></script>
</head>
<body>
<div id="coverage"><svg style="width:1200px;height:400px;"></svg></div>
<script>
coverage=[{"values": [{"y": 0.1, "x": 0}, {"y": 0.1, "x": 1}, {"y": 0.1, "x": 2}, {"y": 0.1, "x": 3}, {"y": 0.1, "x": 4}, {"y": 0.1, "x": 5}, {"y": 0.1, "x": 6}, {"y": 0.1, "x": 7}, {"y": 0.1, "x": 8}, {"y": 0.1, "x": 9}, {"y": 0.1, "x": 10}, {"y": 0.1, "x": 11}, {"y": 0.1, "x": 12}, {"y": 0.1, "x": 13}, {"y": 0.1, "x": 14}, {"y": 0.1, "x": 15}, {"y": 0.1, "x": 16}, {"y": 0.1, "x": 17}, {"y": 0.1, "x": 18}, {"y": 0.1, "x": 19}, {"y": 0.1, "x": 20}, {"y": 0.1, "x": 21}, {"y": 0.1, "x": 22}, {"y": 0.1, "x": 23}, {"y": 0.1, "x": 24}, {"y": 0.1, "x": 25}, {"y": 0.1, "x": 26}, {"y": 0.1, "x": 27}, {"y": 0.1, "x": 28}, {"y": 0.1, "x": 29}, {"y": 0.1, "x": 30}, {"y": 0.1, "x": 31}, {"y": 0.1, "x": 32}, {"y": 0.1, "x": 33}, {"y": 0.1, "x": 34}, {"y": 0.1, "x": 35}, {"y": 0.1, "x": 36}, {"y": 0.1, "x": 37}, {"y": 0.1, "x": 38}, {"y": 0.1, "x": 39}, {"y": 0.1, "x": 40}, {"y": 0.1, "x": 41}, {"y": 0.1, "x": 42}, {"y": 0.1, "x": 43}, {"y": 0.1, "x": 44}, {"y": 0.1, "x": 45}, {"y": 0.1, "x": 46}, {"y": 0.1, "x": 47}, {"y": 0.1, "x": 48}, {"y": 0.1, "x": 49}], "key": "qq003.1.3", "yAxis": "1"}, {"values": [{"y": 0.1, "x": 0}, {"y": 0.1, "x": 1}, {"y": 0.1, "x": 2}, {"y": 0.1, "x": 3}, {"y": 0.1, "x": 4}, {"y": 0.1, "x": 5}, {"y": 0.1, "x": 6}, {"y": 0.1, "x": 7}, {"y": 0.1, "x": 8}, {"y": 0.1, "x": 9}, {"y": 0.1, "x": 10}, {"y": 0.1, "x": 11}, {"y": 0.1, "x": 12}, {"y": 0.1, "x": 13}, {"y": 0.1, "x": 14}, {"y": 0.1, "x": 15}, {"y": 0.1, "x": 16}, {"y": 0.1, "x": 17}, {"y": 0.1, "x": 18}, {"y": 0.1, "x": 19}, {"y": 0.1, "x": 20}, {"y": 0.1, "x": 21}, {"y": 0.1, "x": 22}, {"y": 0.1, "x": 23}, {"y": 0.1, "x": 24}, {"y": 0.1, "x": 25}, {"y": 0.1, "x": 26}, {"y": 0.1, "x": 27}, {"y": 0.1, "x": 28}, {"y": 0.1, "x": 29}, {"y": 0.1, "x": 30}, {"y": 0.1, "x": 31}, {"y": 0.1, "x": 32}, {"y": 0.1, "x": 33}, {"y": 0.1, "x": 34}, {"y": 0.1, "x": 35}, {"y": 0.1, "x": 36}, {"y": 0.1, "x": 37}, {"y": 0.1, "x": 38}, {"y": 0.1, "x": 39}, {"y": 0.1, "x": 40}, {"y": 0.1, "x": 41}, {"y": 0.1, "x": 42}, {"y": 0.1, "x": 43}, {"y": 0.1, "x": 44}, {"y": 0.1, "x": 45}, {"y": 0.1, "x": 46}, {"y": 0.1, "x": 47}, {"y": 0.1, "x": 48}, {"y": 0.1, "x": 49}], "key": "qq003.1.67", "yAxis": "1"}, {"values": [{"y": 247677.0, "x": 0}, {"y": 250012.0, "x": 1}, {"y": 287416.0, "x": 2}, {"y": 327385.0, "x": 3}, {"y": 276739.0, "x": 4}, {"y": 247264.0, "x": 5}, {"y": 234246.0, "x": 6}, {"y": 252476.0, "x": 7}, {"y": 263584.0, "x": 8}, {"y": 244878.0, "x": 9}, {"y": 263238.0, "x": 10}, {"y": 223955.0, "x": 11}, {"y": 147838.0, "x": 12}, {"y": 216088.0, "x": 13}, {"y": 219334.0, "x": 14}, {"y": 464921.0, "x": 15}, {"y": 253147.0, "x": 16}, {"y": 143214.0, "x": 17}, {"y": 263756.0, "x": 18}, {"y": 289643.0, "x": 19}, {"y": 219631.0, "x": 20}, {"y": 280396.0, "x": 21}, {"y": 228272.0, "x": 22}, {"y": 205129.0, "x": 23}, {"y": 277684.0, "x": 24}, {"y": 336930.0, "x": 25}, {"y": 206536.0, "x": 26}, {"y": 184942.0, "x": 27}, {"y": 288391.0, "x": 28}, {"y": 265598.0, "x": 29}, {"y": 214029.0, "x": 30}, {"y": 205708.0, "x": 31}, {"y": 238413.0, "x": 32}, {"y": 173776.0, "x": 33}, {"y": 231737.0, "x": 34}, {"y": 189285.0, "x": 35}, {"y": 299765.0, "x": 36}, {"y": 254324.0, "x": 37}, {"y": 231075.0, "x": 38}, {"y": 425653.0, "x": 39}, {"y": 344885.0, "x": 40}, {"y": 394357.0, "x": 41}, {"y": 184156.0, "x": 42}, {"y": 251984.0, "x": 43}, {"y": 236977.0, "x": 44}, {"y": 280948.0, "x": 45}, {"y": 305699.0, "x": 46}, {"y": 249000.0, "x": 47}, {"y": 281287.0, "x": 48}, {"y": 247748.0, "x": 49}], "key": "qq001.2.047", "yAxis": "1"}];
nv.addGraph(function() {
var chart = nv.models.lineChart();
chart.margin({top: 30, right: 60, bottom: 100, left: 60});
var datum = coverage;
chart.xAxis
.rotateLabels(-25).tickFormat(d3.format(',.1f'));
chart.yAxis
.axisLabel('Coverage').tickFormat(d3.format(',.02f'));
chart.showLegend(true);
d3.select('#coverage svg')
.datum(datum)
.transition().duration(500)
.attr('width', 1200)
.attr('height', 400)
.call(chart);
});
</script>
</body>
</html>
Does anyone know how to fix this?
This is probably because you have identical points in the three series. Refer to https://github.com/novus/nvd3/issues/873
Try using useVoronoi(false);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.7.0/nv.d3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.7.0/nv.d3.min.js"></script>
</head>
<body>
<div id="coverage"><svg style="width:1200px;height:400px;"></svg></div>
<script>
coverage=[{"values": [{"y": 0.2, "x": 0}, {"y": 0.1, "x": 1}, {"y": 0.1, "x": 2}, {"y": 0.1, "x": 3}, {"y": 0.1, "x": 4}, {"y": 0.1, "x": 5}, {"y": 0.1, "x": 6}, {"y": 0.1, "x": 7}, {"y": 0.1, "x": 8}, {"y": 0.1, "x": 9}, {"y": 0.1, "x": 10}, {"y": 0.1, "x": 11}, {"y": 0.1, "x": 12}, {"y": 0.1, "x": 13}, {"y": 0.1, "x": 14}, {"y": 0.1, "x": 15}, {"y": 0.1, "x": 16}, {"y": 0.1, "x": 17}, {"y": 0.1, "x": 18}, {"y": 0.1, "x": 19}, {"y": 0.1, "x": 20}, {"y": 0.1, "x": 21}, {"y": 0.1, "x": 22}, {"y": 0.1, "x": 23}, {"y": 0.1, "x": 24}, {"y": 0.1, "x": 25}, {"y": 0.1, "x": 26}, {"y": 0.1, "x": 27}, {"y": 0.1, "x": 28}, {"y": 0.1, "x": 29}, {"y": 0.1, "x": 30}, {"y": 0.1, "x": 31}, {"y": 0.1, "x": 32}, {"y": 0.1, "x": 33}, {"y": 0.1, "x": 34}, {"y": 0.1, "x": 35}, {"y": 0.1, "x": 36}, {"y": 0.1, "x": 37}, {"y": 0.1, "x": 38}, {"y": 0.1, "x": 39}, {"y": 0.1, "x": 40}, {"y": 0.1, "x": 41}, {"y": 0.1, "x": 42}, {"y": 0.1, "x": 43}, {"y": 0.1, "x": 44}, {"y": 0.1, "x": 45}, {"y": 0.1, "x": 46}, {"y": 0.1, "x": 47}, {"y": 0.1, "x": 48}, {"y": 0.1, "x": 49}], "key": "qq003.1.3", "yAxis": "1"}, {"values": [{"y": 0.1, "x": 0}, {"y": 0.1, "x": 1}, {"y": 0.1, "x": 2}, {"y": 0.1, "x": 3}, {"y": 0.1, "x": 4}, {"y": 0.1, "x": 5}, {"y": 0.1, "x": 6}, {"y": 0.1, "x": 7}, {"y": 0.1, "x": 8}, {"y": 0.1, "x": 9}, {"y": 0.1, "x": 10}, {"y": 0.1, "x": 11}, {"y": 0.1, "x": 12}, {"y": 0.1, "x": 13}, {"y": 0.1, "x": 14}, {"y": 0.1, "x": 15}, {"y": 0.1, "x": 16}, {"y": 0.1, "x": 17}, {"y": 0.1, "x": 18}, {"y": 0.1, "x": 19}, {"y": 0.1, "x": 20}, {"y": 0.1, "x": 21}, {"y": 0.1, "x": 22}, {"y": 0.1, "x": 23}, {"y": 0.1, "x": 24}, {"y": 0.1, "x": 25}, {"y": 0.1, "x": 26}, {"y": 0.1, "x": 27}, {"y": 0.1, "x": 28}, {"y": 0.1, "x": 29}, {"y": 0.1, "x": 30}, {"y": 0.1, "x": 31}, {"y": 0.1, "x": 32}, {"y": 0.1, "x": 33}, {"y": 0.1, "x": 34}, {"y": 0.1, "x": 35}, {"y": 0.1, "x": 36}, {"y": 0.1, "x": 37}, {"y": 0.1, "x": 38}, {"y": 0.1, "x": 39}, {"y": 0.1, "x": 40}, {"y": 0.1, "x": 41}, {"y": 0.1, "x": 42}, {"y": 0.1, "x": 43}, {"y": 0.1, "x": 44}, {"y": 0.1, "x": 45}, {"y": 0.1, "x": 46}, {"y": 0.1, "x": 47}, {"y": 0.1, "x": 48}, {"y": 0.1, "x": 49}], "key": "qq003.1.67", "yAxis": "1"}, {"values": [{"y": 247677.0, "x": 0}, {"y": 250012.0, "x": 1}, {"y": 287416.0, "x": 2}, {"y": 327385.0, "x": 3}, {"y": 276739.0, "x": 4}, {"y": 247264.0, "x": 5}, {"y": 234246.0, "x": 6}, {"y": 252476.0, "x": 7}, {"y": 263584.0, "x": 8}, {"y": 244878.0, "x": 9}, {"y": 263238.0, "x": 10}, {"y": 223955.0, "x": 11}, {"y": 147838.0, "x": 12}, {"y": 216088.0, "x": 13}, {"y": 219334.0, "x": 14}, {"y": 464921.0, "x": 15}, {"y": 253147.0, "x": 16}, {"y": 143214.0, "x": 17}, {"y": 263756.0, "x": 18}, {"y": 289643.0, "x": 19}, {"y": 219631.0, "x": 20}, {"y": 280396.0, "x": 21}, {"y": 228272.0, "x": 22}, {"y": 205129.0, "x": 23}, {"y": 277684.0, "x": 24}, {"y": 336930.0, "x": 25}, {"y": 206536.0, "x": 26}, {"y": 184942.0, "x": 27}, {"y": 288391.0, "x": 28}, {"y": 265598.0, "x": 29}, {"y": 214029.0, "x": 30}, {"y": 205708.0, "x": 31}, {"y": 238413.0, "x": 32}, {"y": 173776.0, "x": 33}, {"y": 231737.0, "x": 34}, {"y": 189285.0, "x": 35}, {"y": 299765.0, "x": 36}, {"y": 254324.0, "x": 37}, {"y": 231075.0, "x": 38}, {"y": 425653.0, "x": 39}, {"y": 344885.0, "x": 40}, {"y": 394357.0, "x": 41}, {"y": 184156.0, "x": 42}, {"y": 251984.0, "x": 43}, {"y": 236977.0, "x": 44}, {"y": 280948.0, "x": 45}, {"y": 305699.0, "x": 46}, {"y": 249000.0, "x": 47}, {"y": 281287.0, "x": 48}, {"y": 247748.0, "x": 49}], "key": "qq001.2.047", "yAxis": "1"}];
nv.addGraph(function() {
var chart = nv.models.lineChart();
chart.margin({top: 30, right: 60, bottom: 100, left: 60});
var datum = coverage;
chart.xAxis
.rotateLabels(-25).tickFormat(d3.format(',.1f'));
chart.yAxis
.axisLabel('Coverage').tickFormat(d3.format(',.02f'));
chart.showLegend(true).useVoronoi(false);
d3.select('#coverage svg')
.datum(datum)
.transition().duration(500)
.attr('width', 1200)
.attr('height', 400)
.call(chart);
});
</script>
</body>
</html>