Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tree-like visualization of the AudioSet Ontology #22

Merged
merged 13 commits into from
Jun 12, 2017
27 changes: 27 additions & 0 deletions datasets/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,33 @@ def paths(node_id, cur=list()):

return hierarchy_paths

def get_taxonomy_as_tree(self):
"""
Returns a dictionary for the tree visualization
"""
keys = [("name", "name"), ("mark", "restrictions")]
def get_all_children(node_id):
# recursive function for adding children in dict
children = self.get_children(node_id)
children_names = []
for child in children:
child_name = {key[0]:child[key[1]] for key in keys}
child_name["children"] = get_all_children(child["id"])
children_names.append(child_name)
if children_names:
return children_names

taxonomy = self.taxonomy
higher_categories = [node for node in taxonomy
if "parent_ids" not in taxonomy[node]]
output_dict = {"name":"Ontology", "children":[]}
for node_id in higher_categories:
dict_level = {key[0]:taxonomy[node_id][key[1]] for key in keys}
dict_level["children"] = get_all_children(node_id)
output_dict["children"].append(dict_level)

return output_dict


class Sound(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
Expand Down
6 changes: 4 additions & 2 deletions datasets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from utils.async_tasks import data_from_async_task
import os
import random
import json


#######################
Expand All @@ -32,6 +33,7 @@ def dataset(request, short_name):
dataset = get_object_or_404(Dataset, short_name=short_name)
user_is_maintainer = dataset.user_is_maintainer(request.user)
form_errors = False
taxonomy_tree = dataset.taxonomy.get_taxonomy_as_tree()
if request.method == 'POST':
form = DatasetReleaseForm(request.POST)
if form.is_valid():
Expand All @@ -44,11 +46,12 @@ def dataset(request, short_name):
form_errors = True
else:
form = DatasetReleaseForm()

return render(request, 'dataset.html', {
'dataset': dataset,
'user_is_maintainer': user_is_maintainer,
'dataset_release_form': form, 'dataset_release_form_errors': form_errors,
'ontology': json.dumps(taxonomy_tree),
})


Expand Down Expand Up @@ -97,7 +100,6 @@ def taxonomy_node(request, short_name, node_id):
node = dataset.taxonomy.get_element_at_id(node_id)
return render(request, 'taxonomy_node.html', {'dataset': dataset, 'node': node})


#############################
# CONTRIBUTE TO DATASET VIEWS
#############################
Expand Down
24 changes: 24 additions & 0 deletions static/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,27 @@ ul {
display: inline-block;
color: white;
}

/* CSS FOR TREE VISUALISATION */
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 2px;
}
.node text {
font-size: 13px;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1px;
}
#ontology_placeholder {
overflow-x: scroll;
width: 100%;
background-color: #FAFAFA;
}

5 changes: 5 additions & 0 deletions static/js/d3.v3.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<script src="{% static "js/dataTables.semanticui.min.js" %}" type="text/javascript"></script>
<script src="{% static "semanticui/semantic.min.js" %}" type="text/javascript"></script>
<script src="{% static "js/main.js" %}" type="text/javascript"></script>
<script src="{% static "js/d3.v3.min.js" %}"></script>
{% block extra_head %}
{% endblock extra_head%}
</head>
Expand Down
38 changes: 37 additions & 1 deletion templates/dataset.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,30 @@
}
}
</script>

<script type="text/javascript">
function openTab(evt, contentName) {
// Declare all variables
var i, tabcontent, tablinks;

// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}

// Get all elements with class="item" and remove the class "active"
tablinks = document.getElementsByClassName("item");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}

// Show the current tab, and add an "active" class to the button that opened the tab
document.getElementById(contentName).style.display = "block";
evt.currentTarget.className += " active";
}
</script>

{% endblock %}
{% block content %}
<h1 class="ui header">About the {{ dataset.name }}</h1>
Expand Down Expand Up @@ -111,7 +135,19 @@ <h1 class="ui header">About the {{ dataset.name }}</h1>
{% endif %}
<br>
<h2>Taxonomy categories</h2>
<div id="dataset_taxonomy_table_placeholder">
<!-- MENU -->
<div class="ui inverted menu">
<a class="item active" onclick="openTab(event, 'dataset_taxonomy_table_placeholder')">
<i class="list layout icon"></i> Table
</a>
<a class="item" onclick="openTab(event, 'dataset_taxonomy_tree_placeholder')">
<i class="tree icon"></i> Tree
</a>
</div>

<!-- Content -->
<div id="dataset_taxonomy_table_placeholder" class="tabcontent">
<div class="ui active centered inline text loader">Loading data...</div>
</div>
{% include "ontology_tree.html" %}
{% endblock %}
138 changes: 138 additions & 0 deletions templates/ontology_tree.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
{% block content %}
<div id="dataset_taxonomy_tree_placeholder" class="tabcontent" style="display: none;">
<div id="ontology_placeholder"></div>
</div>
{% endblock %}

{% block page_js %}
<script type="text/javascript">

var margin = {top: 10, right: 10, bottom: 10, left: 105},
width = 1560 - margin.right - margin.left,
height = 1000 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("#ontology_placeholder").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");


$(document).ready(function(){
show_ontology();
});

function show_ontology(){
root = JSON.parse('{{ontology|escapejs|safe}}');
root.x0 = height / 2;
root.y0 = 0;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
}

d3.select(self.frameElement).style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 340; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
//.attr("x", function(d) { return d.children || d._children ? -10 : 10; })
.attr("x", function(d) { return -10; })
.attr("dy", ".30em")
//.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.attr("text-anchor", function(d) { return "end"; })
.text(function(d) { return d.children || d._children ? d.name+' [+]' : d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r",5)
.style("fill", function(d) {
if (d.mark) {
if (d.mark.includes("omittedTT")) {return "red";}
else {return "#fff";}} } );
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6)
.style("fill", function(d) {
if (d.mark) {
if (d.mark.includes("omittedTT")) {return "red";}
else {return "#fff";}} } );
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
</script>
{% endblock %}