You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
484 lines
14 KiB
484 lines
14 KiB
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<head> |
|
<link rel="stylesheet" type="text/css" href="dependency.css"> |
|
</head> |
|
<body> |
|
<script src="Scripts/d3.v3.js"></script> |
|
<script src="origin.js"></script> |
|
|
|
<!-- ================================================= --> |
|
<!-- ===========ACTUAL HTML ================ --> |
|
<!-- ================================================= --> |
|
|
|
<form id="form"> |
|
<label><input type="range" name="circle_size" min="1" max="50" value="15"/> Circle size</label><br> |
|
<label><input type="range" name="charge_multiplier" min="1" max="500" value="100"/> Charge multiplier</label><br> |
|
<label><input type="range" name="link_strength" min="0.1" max="100" value="7"/> Link strength</label><br> |
|
<label><input type="checkbox" name="show_texts_near_circles" checked="checked"/> Show names</label><br> |
|
<input id="search_input" placeholder="Type regexp to filter nodes" style="width:100%;"><br> |
|
|
|
</form> |
|
<div id="chart"> |
|
<!-- Here the SVG will be placed--> |
|
</div> |
|
|
|
<script src="Scripts/parse.js"></script> |
|
<script> |
|
|
|
// =================================================== |
|
// =============== CONFIGURABLE PARAMS ============== |
|
// =================================================== |
|
|
|
var default_link_distance = 10; |
|
|
|
// How far can we change default_link_distance? |
|
// 0 - I don't care |
|
// 0.5 - Change it as you want, but it's preferrable to have default_link_distance |
|
// 1 - One does not change default_link_distance |
|
var default_link_strength = 0.7; |
|
|
|
// Should I comment this? |
|
var default_circle_radius = 15; |
|
|
|
// you can set it to true, but this will not help to understanf what's going on |
|
var show_texts_near_circles = true; |
|
var default_max_texts_length = 100; |
|
|
|
// Should we use regexp-based grouping or not |
|
var use_regexp_color_grouping_matchers = false; |
|
|
|
var charge_multiplier = 200; |
|
|
|
// Each item thet matches specified regexps will be placed to correspondent group with unique color |
|
var regexp_color_matchers = [ |
|
"^NI", // Nimbus |
|
"^UI", // UIKit |
|
"^NS", // Foundation |
|
"^CA", // Core animations |
|
]; |
|
|
|
var dependecy_graph = objcdv.parse_dependencies_graph(dependencies, use_regexp_color_grouping_matchers ? regexp_color_matchers : null); |
|
|
|
var w = window, |
|
d = document, |
|
e = d.documentElement, |
|
g = d.getElementsByTagName('body')[0], |
|
x = w.innerWidth || e.clientWidth || g.clientWidth, |
|
y = w.innerHeight|| e.clientHeight|| g.clientHeight; |
|
|
|
// =================================================== |
|
// =============== http://d3js.org/ Magic =========== |
|
// =================================================== |
|
|
|
// https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors |
|
var color = d3.scale.category10(); |
|
var selectedIdx = -1 |
|
var selectedType = "normal" |
|
var selectedobject = {} |
|
|
|
var container = d3.select("#chart").append("svg") |
|
.attr("width", x) |
|
.attr("height", y) |
|
|
|
// =================================================== |
|
// =============== ZOOM LOGIC ======================== |
|
// =================================================== |
|
|
|
container.append("rect") |
|
.attr("width", x) |
|
.attr("height", y) |
|
.style("fill", "none") |
|
.style("pointer-events", "all") |
|
.call(d3.behavior.zoom().on("zoom", redraw)) |
|
|
|
function redraw() { |
|
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); |
|
} |
|
|
|
// =================================================== |
|
// =============== FORCE LAYOUT ====================== |
|
// =================================================== |
|
|
|
var force = d3.layout.force() |
|
.charge(function(d) { return d.filtered ? 0 : -d.weight * charge_multiplier}) |
|
.linkDistance(function(l) { return l.source.filtered || l.target.filtered ? 500 : radius(l.source) + radius(l.target) + default_link_distance}) |
|
.size([x, y]) |
|
.nodes(d3.values(dependecy_graph.nodes)) |
|
.links(dependecy_graph.links) |
|
.linkStrength(function(l) { return l.source.filtered || l.target.filtered ? 0 : default_link_strength}) |
|
.start(); |
|
|
|
var svg = container.append('g') |
|
|
|
// =================================================== |
|
// =============== MARKERS SETUP ================== |
|
// =================================================== |
|
|
|
svg.append("defs").selectAll("marker") |
|
.data(["default", "dependency", "dependants"]) |
|
.enter().append("marker") |
|
.attr("id", function(d) { return d; }) |
|
.attr("viewBox", "0 -5 10 10") |
|
.attr("refX", 10) |
|
.attr("refY", 0) |
|
.attr("markerWidth", 10) |
|
.attr("markerHeight", 10) |
|
.attr("orient", "auto") |
|
.attr("class", "marker") |
|
.append("path") |
|
.attr("d", "M0,-5L10,0L0,5"); |
|
|
|
|
|
// =================================================== |
|
// =============== LINKS SETUP ================== |
|
// =================================================== |
|
|
|
var link = svg.append("g").selectAll("path") |
|
.data(dependecy_graph.links) |
|
.enter().append("path") |
|
.attr("class", "link") |
|
.attr("marker-end", "url(#default)") |
|
.style("stroke-width", function(d) { return Math.sqrt(1); }) |
|
|
|
// =================================================== |
|
// =============== NODES SETUP ================== |
|
// =================================================== |
|
|
|
var node = svg.append("g").selectAll("circle.node") |
|
.data(dependecy_graph.nodes) |
|
.enter().append("circle") |
|
.attr("r", radius) |
|
.style("fill", function(d) { return color(d.group) }) |
|
.attr("class", "node") |
|
.attr("source", function(d) { return d.source}) |
|
.attr("dest", function(d) { return d.dest}) |
|
.call(force.drag) |
|
.on("click", select_node) |
|
.on("contextmenu", select_recursively_node) |
|
|
|
// =================================================== |
|
// =============== TEXT NODES SETUP ============= |
|
// =================================================== |
|
|
|
var text = svg.append("g").selectAll("text") |
|
.data(force.nodes()) |
|
.enter().append("text") |
|
.attr("visibility", "visible") |
|
.text(function(d) { return d.name.substring(0, default_max_texts_length) }); |
|
|
|
// =================================================== |
|
// =============== FORCE UPDATE ============= |
|
// =================================================== |
|
|
|
force.on("tick", function() { |
|
svg.selectAll(".node").attr("r", radius); |
|
link.attr("d", link_line); |
|
node.attr("transform", transform ); |
|
if (show_texts_near_circles) { |
|
text.attr("transform", transform); |
|
} |
|
}); |
|
|
|
// =================================================== |
|
// =============== HELPER FUNCTIONS ============= |
|
// =================================================== |
|
function link_line(d) { |
|
var dx = d.target.x - d.source.x, |
|
dy = d.target.y - d.source.y, |
|
dr = Math.sqrt(dx * dx + dy * dy); |
|
|
|
var rsource = radius(d.sourceNode)/dr; |
|
var rdest = radius(d.targetNode)/dr; |
|
var startX = d.source.x + dx * rsource; |
|
var startY = d.source.y + dy * rsource; |
|
|
|
var endX = d.target.x - dx * rdest; |
|
var endY = d.target.y - dy * rdest; |
|
return "M" + startX+ "," + startY + "L" + endX+ "," + endY; |
|
} |
|
|
|
function transform(d) { |
|
return "translate(" + d.x + "," + d.y + ")"; |
|
} |
|
|
|
function radius(d) { |
|
return default_circle_radius + default_circle_radius * d.source / 10; |
|
} |
|
|
|
/* |
|
Window resize update |
|
*/ |
|
w.onresize = function(){ |
|
x = w.innerWidth || e.clientWidth || g.clientWidth ; |
|
y = w.innerHeight|| e.clientHeight|| g.clientHeight; |
|
|
|
container.attr("width", x ).attr("height", y); |
|
force.size([x, y]).start(); |
|
}; |
|
|
|
|
|
// =================================================== |
|
// =============== SELECTING_NODE ============= |
|
// =================================================== |
|
|
|
function deselect_node(d) { |
|
delete d.fixed |
|
selectedIdx = -1 |
|
selectedobject = {}; |
|
svg.selectAll('circle, path, text') |
|
.classed('filtered', false) |
|
.each(function(node) { |
|
node.filtered = false |
|
}) |
|
.transition() |
|
|
|
svg.selectAll('.link') |
|
.attr("marker-end", "url(#default)") |
|
.classed('filtered', false) |
|
.transition() |
|
|
|
|
|
force.start(); |
|
return |
|
} |
|
|
|
function select_node(d) { |
|
if (d3.event.defaultPrevented) return |
|
|
|
// Deselect if needed |
|
if (d.idx == selectedIdx && selectedType == "normal") { deselect_node(d); return } |
|
|
|
// Update selected object |
|
delete selectedobject.fixed |
|
selectedIdx = d.idx |
|
selectedobject = d |
|
selectedobject.fixed = true |
|
selectedType = "normal" |
|
|
|
// Figure out the neighboring node id's with brute strength because the graph is small |
|
var nodeNeighbors = |
|
dependecy_graph.links |
|
.filter(function(link) { |
|
return link.source.index === d.index || link.target.index === d.index;}) |
|
.map(function(link) { |
|
return link.source.index === d.index ? link.target.index : link.source.index; |
|
} |
|
); |
|
|
|
// Fade out all circles |
|
svg.selectAll('circle') |
|
.classed('filtered', true) |
|
.each(function(node){ |
|
node.filtered = true; |
|
node.neighbours = false; |
|
}).transition() |
|
|
|
|
|
svg.selectAll('text') |
|
.classed('filtered', true) |
|
.transition() |
|
|
|
|
|
svg.selectAll('.link'). |
|
transition() |
|
.attr("marker-end", "") |
|
|
|
|
|
// Higlight all circle and texts |
|
svg.selectAll('circle, text') |
|
.filter(function(node) { |
|
return nodeNeighbors.indexOf(node.index) > -1 || node.index == d.index; |
|
}) |
|
.classed('filtered', false) |
|
.each(function(node) { |
|
node.filtered = false; |
|
node.neighbours = true; |
|
}) |
|
.transition() |
|
|
|
// Higlight links |
|
svg.selectAll('.link') |
|
.filter(function(link) { |
|
return link.source.index === d.index || link.target.index == d.index |
|
}) |
|
.classed('filtered', false) |
|
.attr("marker-end", function(l) { return l.source.index === d.index ? "url(#dependency)" : "url(#dependants)"}) |
|
.transition() |
|
|
|
force.start(); |
|
} |
|
|
|
function select_recursively_node(d) { |
|
if (d3.event.defaultPrevented) return |
|
|
|
// Don't show context menu :) |
|
d3.event.preventDefault() |
|
|
|
// Deselect if needed |
|
if (d.idx == selectedIdx && selectedType == "recursive") { deselect_node(d); return } |
|
|
|
// Update selected object |
|
delete selectedobject.fixed |
|
selectedIdx = d.idx |
|
selectedobject = d |
|
selectedobject.fixed = true |
|
selectedType = "recursive" |
|
|
|
// Figure out the neighboring node id's with brute strength because the graph is small |
|
var neighbours = {} |
|
var nodeNeighbors = |
|
dependecy_graph.links |
|
.filter(function(link) { |
|
return link.source.index === d.index}) |
|
.map(function(link) { |
|
var idx = link.source.index === d.index ? link.target.index : link.source.index; |
|
if (link.source.index === d.index) { |
|
console.log("Step 0. Adding ",dependecy_graph.nodes[idx].name) |
|
neighbours[idx] = 1; |
|
} |
|
return idx; |
|
} |
|
); |
|
|
|
// Next part - neighbours of neigbours |
|
var currentsize = Object.keys(neighbours).length |
|
var nextSize = 0; |
|
var step = 1; |
|
while (nextSize != currentsize) { |
|
console.log("Current size " + currentsize + " Next size is " + nextSize) |
|
currentsize = nextSize |
|
dependecy_graph.links |
|
.filter(function(link) { |
|
return neighbours[link.source.index] != undefined}) |
|
.map(function(link) { |
|
var idx = link.target.index; |
|
console.log("Step "+step+". Adding ",dependecy_graph.nodes[idx].name + " From " + dependecy_graph.nodes[link.source.index].name ) |
|
|
|
neighbours[idx] = 1; |
|
return idx; |
|
} |
|
); |
|
nextSize = Object.keys(neighbours).length |
|
step = step + 1 |
|
} |
|
|
|
neighbours[d.index] = 1 |
|
nodeNeighbors = Object.keys(neighbours).map(function(neibour) { |
|
return parseInt(neibour); |
|
}) |
|
|
|
|
|
// Fade out all circles |
|
svg.selectAll('circle') |
|
.classed('filtered', true) |
|
.each(function(node){ |
|
node.filtered = true; |
|
node.neighbours = false; |
|
}).transition() |
|
|
|
|
|
svg.selectAll('text') |
|
.classed('filtered', true) |
|
.transition() |
|
|
|
|
|
svg.selectAll('.link'). |
|
transition() |
|
.attr("marker-end", "") |
|
|
|
|
|
// Higlight all circle and texts |
|
svg.selectAll('circle, text') |
|
.filter(function(node) { |
|
return nodeNeighbors.indexOf(node.index) > -1 || node.index == d.index; |
|
}) |
|
.classed('filtered', false) |
|
.each(function(node) { |
|
node.filtered = false; |
|
node.neighbours = true; |
|
}) |
|
.transition() |
|
|
|
// Higlight links |
|
svg.selectAll('.link') |
|
.filter(function(link) { |
|
return nodeNeighbors.indexOf(link.source.index) > -1 |
|
}) |
|
.classed('filtered', false) |
|
.attr("marker-end", function(l) { return l.source.index === d.index ? "url(#dependency)" : "url(#dependants)"}) |
|
.transition() |
|
|
|
force.start(); |
|
} |
|
|
|
</script> |
|
|
|
<script> |
|
// =================================================== |
|
// =============== INPUTS HANDLING ============== |
|
// =================================================== |
|
d3.selectAll("input").on("change", function change() { |
|
|
|
if (this.name == "circle_size") { |
|
default_circle_radius = parseInt(this.value); |
|
force.linkDistance(function(l) { return radius(l.source) + radius(l.target) + default_link_distance;}) |
|
force.start(); |
|
} |
|
|
|
if (this.name == "charge_multiplier") { |
|
charge_multiplier = parseInt(this.value); |
|
force.start(); |
|
} |
|
|
|
if (this.name == "link_strength") { |
|
default_link_strength = parseInt(this.value) / 10; |
|
force.linkStrength(default_link_strength); |
|
force.start(); |
|
} |
|
|
|
if (this.name == "show_texts_near_circles") { |
|
text.attr("visibility", this.checked ? "visible" : "hidden") |
|
show_texts_near_circles = this.checked |
|
force.start(); |
|
} |
|
|
|
}); |
|
</script> |
|
|
|
|
|
<script> |
|
// =================================================== |
|
// =============== LIVE FILTERING ============== |
|
// =================================================== |
|
d3.select("#search_input").on("input", function () { |
|
// Filter all items |
|
console.log("Input changed to" + this.value) |
|
deselect_node(selectedobject); |
|
|
|
if (this.value && this.value.length) { |
|
var re = new RegExp(this.value, "i"); |
|
svg.selectAll('circle, text') |
|
.classed('filtered', function(node) { |
|
var filtered = !node.name.match(re); |
|
node.filtered = filtered; |
|
node.neighbours = !filtered; |
|
return filtered; |
|
}) |
|
.transition() |
|
|
|
svg.selectAll('.link') |
|
.classed('filtered', function(l) { |
|
var filtered = !(l.sourceNode.name.match(re) && l.targetNode.name.match(re)); |
|
return filtered; |
|
}) |
|
.attr("marker-end", function (l) { |
|
var filtered = !(l.sourceNode.name.match(re) && l.targetNode.name.match(re)); |
|
return filtered ? "" : "url(#default)" |
|
}) |
|
.transition() |
|
|
|
force.start(); |
|
} |
|
}); |
|
</script> |