Commit e12ee580 authored by Sylvain Schmitz's avatar Sylvain Schmitz

added matrix visualization

parent 149f4485
......@@ -3,17 +3,31 @@
<meta charset="utf-8">
<title>Flows Between XPath Fragments</title>
<style>
body {
margin: 1em auto 4em auto;
position: relative;
width: 900px;
}
svg {
display: block;
float: left;
margin: 1em;
font-family: sans-serif;
}
#chord { width: 720px; height: 720px; }
#circle circle {
fill: none;
pointer-events: all;
}
.group path {
#chord .group path {
fill-opacity: .5;
}
path.chord {
#chord path.chord {
stroke: #000;
stroke-width: .25px;
}
......@@ -22,6 +36,9 @@ path.chord {
display: none;
}
#matrix, aside, aside select { font: 10px sans-serif; color: #333; }
#matrix line { stroke: #fff; }
</style>
<h1>Outflows in @benchmark@</h1>
......@@ -30,12 +47,182 @@ path.chord {
other. Thicker links represent larger flows. Links are directed;
the colour indicates the most important flow.
<p>Built with <a href="http://d3js.org/">D3</a>.</aside>
<p>Built with <a href="http://d3js.org/">D3</a>.
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="https://d3js.org/queue.v1.min.js"></script>
<aside style="margin-top:0px; margin-left:-160px">
<p>Order: <select id="order">
<option value="name">by Name</option>
<option value="count">by Frequency</option>
<option value="group">by Cluster</option>
</select>
</aside>
<script>
var margin = {top: 80, right: 0, bottom: 10, left: 80},
matrixw = 135,
matrixh = 135;
var x = d3.scaleBand().range([0, matrixw]),
z = d3.scaleLinear().range([0.09,1]).clamp(true);
var matrixsvg = d3.select("body").append("svg")
.attr("id", "matrix")
.attr("width", matrixw + margin.left + margin.right)
.attr("height", matrixh + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.style("margin-top", 20 - margin.top + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
queue()
.defer(d3.csv, "fragments.csv")
.defer(d3.json, "matrix.json")
.await(ready);
function ready(error, fragments, matrix) {
if (error) throw error;
var mx = [], n = fragments.length;
// Compute index per node.
fragments.forEach(function(d, i) {
mx[i] = d3.range(n).map(function(j) { return {x: j, y: i, z: 0}; });
});
// maximal number of entries
var maxentries = 0;
for (let f of fragments)
maxentries = (maxentries < parseInt(f.entries))?
parseInt(f.entries): maxentries;
// Convert matrix; count character occurrences.
var maxdiff = 0;
for (var i = 0; i < n; i++)
for (var j = 0; j < n; j++) {
maxdiff = (maxdiff < matrix[i][j])?
matrix[i][j]: maxdiff;
mx[i][j].z += matrix[i][j];
}
z.domain([0, maxdiff]);
// Precompute the orders.
var orders = {
name: d3.range(n).sort(function(a, b) { return d3.ascending(fragments[a].name, fragments[b].name); }),
count: d3.range(n).sort(function(a, b) { return parseInt(fragments[b].entries) - parseInt(fragments[a].entries); }),
group: d3.range(n).sort(function(a, b) { return d3.ascending(fragments[a].color, fragments[b].color); })
};
// The default sort order.
x.domain(orders.name);
matrixsvg.append("rect")
.attr("class", "background")
.attr("width", matrixw)
.attr("height", matrixh)
.style("fill", "#fbfbfb");
var row = matrixsvg.selectAll(".row")
.data(mx)
.enter().append("g")
.attr("class", "row")
.attr("transform", function(d, i) { return "translate(0," + x(i) + ")"; })
.each(row);
row.append("line")
.attr("x2", matrixw);
var rtext = row.append("text")
.attr("x", -6)
.attr("y", x.bandwidth() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "end")
.text(function(d, i) { return fragments[i].name; });
rtext.append("title").text(function(d, i) {
return fragments[i].name + ": " + fragments[i].entries + " entries" + " ("
+ formatPercent(parseInt(fragments[i].entries) / maxentries)
+ ")";
});
var column = matrixsvg.selectAll(".column")
.data(mx)
.enter().append("g")
.attr("class", "column")
.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });
column.append("line")
.attr("x1", -matrixw);
var ctext = column.append("text")
.attr("x", 6)
.attr("y", x.bandwidth() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "start")
.text(function(d, i) { return fragments[i].name; });
ctext.append("title").text(function(d, i) {
return fragments[i].name + ": " + fragments[i].entries + " entries" + " ("
+ formatPercent(parseInt(fragments[i].entries) / maxentries)
+ ")";
});
function row(row) {
var cell = d3.select(this).selectAll(".cell")
.data(row.filter(function(d) { return d.z; }))
.enter().append("rect")
.attr("class", "cell")
.attr("x", function(d) { return x(d.x); })
.attr("width", x.bandwidth())
.attr("height", x.bandwidth())
.style("fill-opacity", function(d) { return z(d.z); })
.style("fill", function(d) { return (parseInt(fragments[d.x].entries) > parseInt(fragments[d.y].entries)) ? fragments[d.x].color : fragments[d.y].color; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
cell.append("title").text(function(d) {
return "| " + fragments[d.y].name
+ " \\ " + fragments[d.x].name
+ " | = " + d.z;
});
}
function mouseover(p) {
d3.selectAll(".row text").classed("active", function(d, i) { return i == p.y; });
d3.selectAll(".column text").classed("active", function(d, i) { return i == p.x; });
}
function mouseout() {
d3.selectAll("text").classed("active", false);
}
d3.select("#order").on("change", function() {
clearTimeout(timeout);
order(this.value);
});
function order(value) {
x.domain(orders[value]);
var t = matrixsvg.transition().duration(2500);
t.selectAll(".row")
.delay(function(d, i) { return x(i) * 4; })
.attr("transform", function(d, i) { return "translate(0," + x(i) + ")"; })
.selectAll(".cell")
.delay(function(d) { return x(d.x) * 4; })
.attr("x", function(d) { return x(d.x); });
t.selectAll(".column")
.delay(function(d, i) { return x(i) * 4; })
.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });
}
var timeout = setTimeout(function() {
order("group");
d3.select("#order").property("selectedIndex", 2).node().focus();
}, 5000);
}
</script>
<script>
var width = 720,
height = 720,
outerRadius = Math.min(width, height) / 2 - 10,
......@@ -55,14 +242,16 @@ var layout = d3.chord()
var path = d3.ribbon()
.radius(innerRadius);
var svg = d3.select("body").append("svg")
var chordsvg = d3.select("body").append("svg")
.style("margin-top", 20 - margin.top + "px")
.attr("id", "chord")
.attr("width", width)
.attr("height", height)
.append("g")
.append("g")
.attr("id", "circle")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("circle")
chordsvg.append("circle")
.attr("r", outerRadius);
queue()
......@@ -170,7 +359,7 @@ function ready(error, fragments, matrix) {
});
// Add a group per neighborhood.
var group = svg.selectAll(".group")
var group = chordsvg.selectAll(".group")
.data(layout.groups)
.enter().append("g")
.attr("class", "group")
......@@ -202,7 +391,7 @@ function ready(error, fragments, matrix) {
//groupText.filter(function(d, i) { return groupPath[0][i].getTotalLength() / 2 - 16 < this.getComputedTextLength(); }).remove();
// Add the chords.
var chord = svg.selectAll(".chord")
var chord = chordsvg.selectAll(".chord")
.data(layout.chords)
.enter().append("path")
.attr("class", "chord")
......@@ -226,5 +415,5 @@ function ready(error, fragments, matrix) {
});
}
}
</script>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment