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

<!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>