Last active
June 13, 2016 13:25
-
-
Save kleem/6b35690b61a9db29686a96b1eadb5539 to your computer and use it in GitHub Desktop.
Filtered matrix
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
svg = d3.select 'svg' | |
width = svg.node().getBoundingClientRect().width | |
height = svg.node().getBoundingClientRect().height | |
matrix_space_width = 0.8 * width | |
matrix_space_height = 0.8 * height | |
# Group for zoomable content | |
zoomable_layer = svg.append('g') | |
# Zoom behavior definition | |
zoom = d3.behavior.zoom() | |
.scaleExtent [0,4] | |
.on 'zoom', () -> | |
zoomable_layer | |
.attr | |
transform: "translate(#{zoom.translate()})scale(#{zoom.scale()})" | |
svg.call(zoom) | |
matrix = zoomable_layer.append 'g' | |
# DATA | |
# ---- | |
data_a = [ | |
{id: 1, label: 'A'}, | |
{id: 2, label: 'B'}, | |
{id: 3, label: 'C'}, | |
{id: 4, label: 'D'}, | |
{id: 5, label: 'E'} | |
] | |
data_b = [ | |
{id: 1, label: 'W'}, | |
{id: 2, label: 'X'}, | |
{id: 3, label: 'Y'}, | |
{id: 4, label: 'Z'} | |
] | |
data_cells = [ | |
{a_id: 1, b_id: 1, weight: 2}, | |
{a_id: 1, b_id: 2, weight: 12}, | |
{a_id: 1, b_id: 3, weight: 1}, | |
{a_id: 1, b_id: 4, weight: 22}, | |
{a_id: 2, b_id: 1, weight: 10}, | |
{a_id: 2, b_id: 2, weight: 1}, | |
{a_id: 2, b_id: 4, weight: 8}, | |
{a_id: 3, b_id: 1, weight: 1}, | |
{a_id: 3, b_id: 2, weight: 8}, | |
{a_id: 3, b_id: 3, weight: 6}, | |
{a_id: 4, b_id: 4, weight: 7}, | |
{a_id: 5, b_id: 1, weight: 2}, | |
{a_id: 5, b_id: 2, weight: 2}, | |
{a_id: 5, b_id: 3, weight: 5}, | |
{a_id: 5, b_id: 4, weight: 3} | |
] | |
# objectify the matrix | |
data_a_index = {} | |
data_a.forEach (d) -> data_a_index[d.id] = d | |
data_b_index = {} | |
data_b.forEach (d) -> data_b_index[d.id] = d | |
data_a.forEach (d) -> d.cells = [] | |
data_b.forEach (d) -> d.cells = [] | |
data_cells.forEach (d) -> | |
d.a = data_a_index[d.a_id] | |
d.b = data_b_index[d.b_id] | |
d.a.cells.push d | |
d.b.cells.push d | |
# selection functions | |
selected = | |
as: {} | |
bs: {} | |
toggle = (d, abs) -> | |
if d.id of selected[abs] | |
delete selected[abs][d.id] | |
else | |
selected[abs][d.id] = d | |
update_selection() | |
update_selection = () -> | |
data_a.forEach (d) -> d.selected = d.id of selected.as or Object.keys(selected.as).length is 0 | |
data_b.forEach (d) -> d.selected = d.id of selected.bs or Object.keys(selected.bs).length is 0 | |
data_cells.forEach (d) -> d.selected = d.a.selected and d.b.selected | |
update_selection() | |
# FILTER | |
# ------ | |
filter_active = false | |
# VIS | |
# --- | |
LABEL_PAD = 8 | |
ANIMATION_DURATION = 800 | |
redraw = () -> | |
if filter_active | |
filtered_data_cells = data_cells.filter (d) -> d.selected | |
filtered_data_a = data_a.filter (d) -> d.selected | |
filtered_data_b = data_b.filter (d) -> d.selected | |
else | |
filtered_data_cells = data_cells | |
filtered_data_a = data_a | |
filtered_data_b = data_b | |
# LAYOUT | |
cell = Math.min(matrix_space_width / filtered_data_b.length, matrix_space_height / filtered_data_a.length) | |
matrix_width = cell * filtered_data_b.length | |
matrix_height = cell * filtered_data_a.length | |
x = d3.scale.ordinal() | |
.domain(filtered_data_b.map (d) -> d.id) | |
.rangeBands([0, matrix_width]) | |
y = d3.scale.ordinal() | |
.domain(filtered_data_a.map (d) -> d.id) | |
.rangeBands([0, matrix_height]) | |
radius = d3.scale.sqrt() | |
.domain([0, d3.max data_cells, (d) -> d.weight]) | |
.range([0, cell/2]) | |
# VIS | |
matrix.transition() | |
.delay(ANIMATION_DURATION) | |
.duration(ANIMATION_DURATION) | |
.attr | |
transform: "translate(#{width/2-matrix_width/2},#{height/2-matrix_height/2})" | |
cells = matrix.selectAll(".cell") | |
.data filtered_data_cells, (d) -> "#{d.a.id}__#{d.b.id}" | |
cells.enter().append("circle") | |
.attr | |
class: "cell" | |
opacity: 0 | |
cells | |
.classed 'selected', (d) -> d.selected | |
# ANIMATIONS | |
cells.exit().transition() | |
.duration(ANIMATION_DURATION) | |
.attr | |
opacity: 0 | |
.remove() | |
cells.transition() | |
.delay(ANIMATION_DURATION) | |
.duration(ANIMATION_DURATION) | |
.attr | |
cx: (d) -> x(d.b.id)+cell/2 | |
cy: (d) -> y(d.a.id)+cell/2 | |
r: (d) -> radius(d.weight) | |
cells.transition() | |
.delay(2*ANIMATION_DURATION) | |
.duration(ANIMATION_DURATION) | |
.attr | |
opacity: 1 | |
# labels | |
x_labels = matrix.selectAll(".x_label") | |
.data filtered_data_b, (d) -> d.id | |
enter_x_labels = x_labels.enter().append("text") | |
.text (d) -> d.label | |
.attr | |
class: "x_label" | |
x: 0 | |
y: -LABEL_PAD | |
opacity: 0 | |
.on 'click', (d) -> | |
if not filter_active | |
toggle(d,'bs') | |
redraw() | |
x_labels | |
.classed 'selected', (d) -> d.selected | |
# ANIMATIONS | |
x_labels.exit().transition() | |
.duration(ANIMATION_DURATION) | |
.attr | |
opacity: 0 | |
.remove() | |
x_labels.transition() | |
.delay(ANIMATION_DURATION) | |
.duration(ANIMATION_DURATION) | |
.attr | |
x: (d) -> x(d.id)+cell/2 | |
enter_x_labels.transition() | |
.delay(2*ANIMATION_DURATION) | |
.duration(ANIMATION_DURATION) | |
.attr | |
opacity: 1 | |
y_labels = matrix.selectAll(".y_label") | |
.data filtered_data_a, (d) -> d.id | |
enter_y_labels = y_labels.enter().append("text") | |
.text (d) -> d.label | |
.attr | |
class: "y_label" | |
x: -LABEL_PAD | |
y: 0 | |
dy: '0.35em' | |
opacity: 0 | |
.on 'click', (d) -> | |
if not filter_active | |
toggle(d,'as') | |
redraw() | |
y_labels | |
.classed 'selected', (d) -> d.selected | |
# ANIMATIONS | |
y_labels.exit().transition() | |
.duration(ANIMATION_DURATION) | |
.attr | |
opacity: 0 | |
.remove() | |
y_labels.transition() | |
.delay(ANIMATION_DURATION) | |
.duration(ANIMATION_DURATION) | |
.attr | |
y: (d) -> y(d.id)+cell/2 | |
enter_y_labels.transition() | |
.delay(2*ANIMATION_DURATION) | |
.duration(ANIMATION_DURATION) | |
.attr | |
opacity: 1 | |
# FILTER reprise | |
# -------------- | |
d3.select('.filter_box > input').on 'click', () -> | |
filter_active = not filter_active | |
redraw() | |
redraw() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html, body { | |
padding: 0; | |
margin: 0; | |
width: 100%; | |
height: 100%; | |
} | |
svg { | |
width: 100%; | |
height: 100%; | |
background: white; | |
} | |
.x_label, .y_label { | |
font-family: sans-serif; | |
font-size: 24px; | |
font-weight: bold; | |
cursor: pointer; | |
} | |
.x_label { | |
text-anchor: middle; | |
} | |
.y_label { | |
text-anchor: end; | |
} | |
.cell, .x_label, .y_label { | |
fill: #BBB; | |
} | |
.selected { | |
fill: black; | |
} | |
.filter_box { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
font-family: sans-serif; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Filtered matrix</title> | |
<link type="text/css" href="index.css" rel="stylesheet"/> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
</head> | |
<body> | |
<svg></svg> | |
<div class="filter_box"><input type="checkbox"/>Hide unselected</div> | |
<script src="index.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.10.0 | |
(function() { | |
var ANIMATION_DURATION, LABEL_PAD, data_a, data_a_index, data_b, data_b_index, data_cells, filter_active, height, matrix, matrix_space_height, matrix_space_width, redraw, selected, svg, toggle, update_selection, width, zoom, zoomable_layer; | |
svg = d3.select('svg'); | |
width = svg.node().getBoundingClientRect().width; | |
height = svg.node().getBoundingClientRect().height; | |
matrix_space_width = 0.8 * width; | |
matrix_space_height = 0.8 * height; | |
zoomable_layer = svg.append('g'); | |
zoom = d3.behavior.zoom().scaleExtent([0, 4]).on('zoom', function() { | |
return zoomable_layer.attr({ | |
transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")" | |
}); | |
}); | |
svg.call(zoom); | |
matrix = zoomable_layer.append('g'); | |
data_a = [ | |
{ | |
id: 1, | |
label: 'A' | |
}, { | |
id: 2, | |
label: 'B' | |
}, { | |
id: 3, | |
label: 'C' | |
}, { | |
id: 4, | |
label: 'D' | |
}, { | |
id: 5, | |
label: 'E' | |
} | |
]; | |
data_b = [ | |
{ | |
id: 1, | |
label: 'W' | |
}, { | |
id: 2, | |
label: 'X' | |
}, { | |
id: 3, | |
label: 'Y' | |
}, { | |
id: 4, | |
label: 'Z' | |
} | |
]; | |
data_cells = [ | |
{ | |
a_id: 1, | |
b_id: 1, | |
weight: 2 | |
}, { | |
a_id: 1, | |
b_id: 2, | |
weight: 12 | |
}, { | |
a_id: 1, | |
b_id: 3, | |
weight: 1 | |
}, { | |
a_id: 1, | |
b_id: 4, | |
weight: 22 | |
}, { | |
a_id: 2, | |
b_id: 1, | |
weight: 10 | |
}, { | |
a_id: 2, | |
b_id: 2, | |
weight: 1 | |
}, { | |
a_id: 2, | |
b_id: 4, | |
weight: 8 | |
}, { | |
a_id: 3, | |
b_id: 1, | |
weight: 1 | |
}, { | |
a_id: 3, | |
b_id: 2, | |
weight: 8 | |
}, { | |
a_id: 3, | |
b_id: 3, | |
weight: 6 | |
}, { | |
a_id: 4, | |
b_id: 4, | |
weight: 7 | |
}, { | |
a_id: 5, | |
b_id: 1, | |
weight: 2 | |
}, { | |
a_id: 5, | |
b_id: 2, | |
weight: 2 | |
}, { | |
a_id: 5, | |
b_id: 3, | |
weight: 5 | |
}, { | |
a_id: 5, | |
b_id: 4, | |
weight: 3 | |
} | |
]; | |
data_a_index = {}; | |
data_a.forEach(function(d) { | |
return data_a_index[d.id] = d; | |
}); | |
data_b_index = {}; | |
data_b.forEach(function(d) { | |
return data_b_index[d.id] = d; | |
}); | |
data_a.forEach(function(d) { | |
return d.cells = []; | |
}); | |
data_b.forEach(function(d) { | |
return d.cells = []; | |
}); | |
data_cells.forEach(function(d) { | |
d.a = data_a_index[d.a_id]; | |
d.b = data_b_index[d.b_id]; | |
d.a.cells.push(d); | |
return d.b.cells.push(d); | |
}); | |
selected = { | |
as: {}, | |
bs: {} | |
}; | |
toggle = function(d, abs) { | |
if (d.id in selected[abs]) { | |
delete selected[abs][d.id]; | |
} else { | |
selected[abs][d.id] = d; | |
} | |
return update_selection(); | |
}; | |
update_selection = function() { | |
data_a.forEach(function(d) { | |
return d.selected = d.id in selected.as || Object.keys(selected.as).length === 0; | |
}); | |
data_b.forEach(function(d) { | |
return d.selected = d.id in selected.bs || Object.keys(selected.bs).length === 0; | |
}); | |
return data_cells.forEach(function(d) { | |
return d.selected = d.a.selected && d.b.selected; | |
}); | |
}; | |
update_selection(); | |
filter_active = false; | |
LABEL_PAD = 8; | |
ANIMATION_DURATION = 800; | |
redraw = function() { | |
var cell, cells, enter_x_labels, enter_y_labels, filtered_data_a, filtered_data_b, filtered_data_cells, matrix_height, matrix_width, radius, x, x_labels, y, y_labels; | |
if (filter_active) { | |
filtered_data_cells = data_cells.filter(function(d) { | |
return d.selected; | |
}); | |
filtered_data_a = data_a.filter(function(d) { | |
return d.selected; | |
}); | |
filtered_data_b = data_b.filter(function(d) { | |
return d.selected; | |
}); | |
} else { | |
filtered_data_cells = data_cells; | |
filtered_data_a = data_a; | |
filtered_data_b = data_b; | |
} | |
cell = Math.min(matrix_space_width / filtered_data_b.length, matrix_space_height / filtered_data_a.length); | |
matrix_width = cell * filtered_data_b.length; | |
matrix_height = cell * filtered_data_a.length; | |
x = d3.scale.ordinal().domain(filtered_data_b.map(function(d) { | |
return d.id; | |
})).rangeBands([0, matrix_width]); | |
y = d3.scale.ordinal().domain(filtered_data_a.map(function(d) { | |
return d.id; | |
})).rangeBands([0, matrix_height]); | |
radius = d3.scale.sqrt().domain([ | |
0, d3.max(data_cells, function(d) { | |
return d.weight; | |
}) | |
]).range([0, cell / 2]); | |
matrix.transition().delay(ANIMATION_DURATION).duration(ANIMATION_DURATION).attr({ | |
transform: "translate(" + (width / 2 - matrix_width / 2) + "," + (height / 2 - matrix_height / 2) + ")" | |
}); | |
cells = matrix.selectAll(".cell").data(filtered_data_cells, function(d) { | |
return d.a.id + "__" + d.b.id; | |
}); | |
cells.enter().append("circle").attr({ | |
"class": "cell", | |
opacity: 0 | |
}); | |
cells.classed('selected', function(d) { | |
return d.selected; | |
}); | |
cells.exit().transition().duration(ANIMATION_DURATION).attr({ | |
opacity: 0 | |
}).remove(); | |
cells.transition().delay(ANIMATION_DURATION).duration(ANIMATION_DURATION).attr({ | |
cx: function(d) { | |
return x(d.b.id) + cell / 2; | |
}, | |
cy: function(d) { | |
return y(d.a.id) + cell / 2; | |
}, | |
r: function(d) { | |
return radius(d.weight); | |
} | |
}); | |
cells.transition().delay(2 * ANIMATION_DURATION).duration(ANIMATION_DURATION).attr({ | |
opacity: 1 | |
}); | |
x_labels = matrix.selectAll(".x_label").data(filtered_data_b, function(d) { | |
return d.id; | |
}); | |
enter_x_labels = x_labels.enter().append("text").text(function(d) { | |
return d.label; | |
}).attr({ | |
"class": "x_label", | |
x: 0, | |
y: -LABEL_PAD, | |
opacity: 0 | |
}).on('click', function(d) { | |
if (!filter_active) { | |
toggle(d, 'bs'); | |
return redraw(); | |
} | |
}); | |
x_labels.classed('selected', function(d) { | |
return d.selected; | |
}); | |
x_labels.exit().transition().duration(ANIMATION_DURATION).attr({ | |
opacity: 0 | |
}).remove(); | |
x_labels.transition().delay(ANIMATION_DURATION).duration(ANIMATION_DURATION).attr({ | |
x: function(d) { | |
return x(d.id) + cell / 2; | |
} | |
}); | |
enter_x_labels.transition().delay(2 * ANIMATION_DURATION).duration(ANIMATION_DURATION).attr({ | |
opacity: 1 | |
}); | |
y_labels = matrix.selectAll(".y_label").data(filtered_data_a, function(d) { | |
return d.id; | |
}); | |
enter_y_labels = y_labels.enter().append("text").text(function(d) { | |
return d.label; | |
}).attr({ | |
"class": "y_label", | |
x: -LABEL_PAD, | |
y: 0, | |
dy: '0.35em', | |
opacity: 0 | |
}).on('click', function(d) { | |
if (!filter_active) { | |
toggle(d, 'as'); | |
return redraw(); | |
} | |
}); | |
y_labels.classed('selected', function(d) { | |
return d.selected; | |
}); | |
y_labels.exit().transition().duration(ANIMATION_DURATION).attr({ | |
opacity: 0 | |
}).remove(); | |
y_labels.transition().delay(ANIMATION_DURATION).duration(ANIMATION_DURATION).attr({ | |
y: function(d) { | |
return y(d.id) + cell / 2; | |
} | |
}); | |
return enter_y_labels.transition().delay(2 * ANIMATION_DURATION).duration(ANIMATION_DURATION).attr({ | |
opacity: 1 | |
}); | |
}; | |
d3.select('.filter_box > input').on('click', function() { | |
filter_active = !filter_active; | |
return redraw(); | |
}); | |
redraw(); | |
}).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment