Commit 351c79d5 authored by Janez K's avatar Janez K

hierarchical clustering widget

parent 6b1bbb24
......@@ -895,6 +895,184 @@
"description": ""
}
},
{
"pk": 103,
"model": "workflows.abstractwidget",
"fields": {
"category": 19,
"treeview_image": "",
"name": "Hierarchical clustering",
"is_streaming": false,
"uid": "12b0d57d-1cbc-4e9d-8087-2dcd8ced2a27",
"interaction_view": "cforange_hierarchical_clustering",
"image": "",
"package": "cforange",
"static_image": "",
"post_interact_action": "cforange_hierarchical_clustering_finished",
"user": null,
"visualization_view": "",
"action": "cforange_hierarchical_clustering",
"wsdl_method": "",
"wsdl": "",
"interactive": true,
"has_progress_bar": false,
"order": 1,
"description": ""
}
},
{
"pk": 210,
"model": "workflows.abstractinput",
"fields": {
"widget": 103,
"name": "Distance Matrix",
"short_name": "dm",
"uid": "bc238f40-be7b-48c4-a526-f17d461a1e10",
"default": "",
"required": false,
"multi": false,
"parameter_type": null,
"variable": "dm",
"parameter": false,
"order": 1,
"description": ""
}
},
{
"pk": 211,
"model": "workflows.abstractinput",
"fields": {
"widget": 103,
"name": "Linkage",
"short_name": "lin",
"uid": "9804f098-f6b6-4858-9d83-df2a45e6174a",
"default": "0",
"required": false,
"multi": false,
"parameter_type": "select",
"variable": "linkage",
"parameter": true,
"order": 2,
"description": ""
}
},
{
"pk": 64,
"model": "workflows.abstractoption",
"fields": {
"uid": "e5a0e0cd-aefe-4d2f-b2dd-26c54aecda41",
"abstract_input": 211,
"value": "1",
"name": "Average linkage"
}
},
{
"pk": 66,
"model": "workflows.abstractoption",
"fields": {
"uid": "e7ab085c-59a2-469b-b4e0-aeef40685a1f",
"abstract_input": 211,
"value": "3",
"name": "Complete linkage"
}
},
{
"pk": 63,
"model": "workflows.abstractoption",
"fields": {
"uid": "e751309c-0f24-409e-b21e-94692c3fa5cf",
"abstract_input": 211,
"value": "0",
"name": "Single linkage"
}
},
{
"pk": 65,
"model": "workflows.abstractoption",
"fields": {
"uid": "16d7b4ca-b14d-4a93-bda3-ca3478efd316",
"abstract_input": 211,
"value": "2",
"name": "Ward's linkage"
}
},
{
"pk": 212,
"model": "workflows.abstractinput",
"fields": {
"widget": 103,
"name": "Visualization type",
"short_name": "viz",
"uid": "f0c1c618-f7c4-4b9c-8780-d03e1ab29834",
"default": "circle",
"required": false,
"multi": false,
"parameter_type": "select",
"variable": "visualization",
"parameter": true,
"order": 3,
"description": ""
}
},
{
"pk": 67,
"model": "workflows.abstractoption",
"fields": {
"uid": "ea7c03eb-b596-4b7f-82ea-840ca44daaad",
"abstract_input": 212,
"value": "circle",
"name": "Circle"
}
},
{
"pk": 68,
"model": "workflows.abstractoption",
"fields": {
"uid": "4bd2311e-3051-451e-8c47-5bb526a885e6",
"abstract_input": 212,
"value": "tree",
"name": "Tree"
}
},
{
"pk": 114,
"model": "workflows.abstractoutput",
"fields": {
"widget": 103,
"name": "Centroids",
"short_name": "ctr",
"variable": "centroids",
"uid": "da9b8816-0ed4-4159-8a7e-ff915d5eb2d2",
"order": 1,
"description": ""
}
},
{
"pk": 115,
"model": "workflows.abstractoutput",
"fields": {
"widget": 103,
"name": "Selected examples",
"short_name": "sel",
"variable": "selected_examples",
"uid": "2055c99d-b2cd-4270-94e7-688eb86ae00b",
"order": 2,
"description": ""
}
},
{
"pk": 116,
"model": "workflows.abstractoutput",
"fields": {
"widget": 103,
"name": "Unselected examples",
"short_name": "uns",
"variable": "unselected_examples",
"uid": "d23df6ab-e4ec-4c09-8d4b-9457f666efca",
"order": 3,
"description": ""
}
},
{
"pk": 12,
"model": "workflows.category",
......
......@@ -7,12 +7,26 @@ def cforange_filter_integers(request,input_dict,output_dict,widget):
def cforange_hierarchical_clustering(request,input_dict,output_dict,widget):
import orange
matrix = input_dict['dm']
root = orange.HierarchicalClustering(matrix, linkage=orange.HierarchicalClustering.Average)
linkage = int(input_dict['linkage'])
linkages = [("Single linkage", orange.HierarchicalClustering.Single),
("Average linkage", orange.HierarchicalClustering.Average),
("Ward's linkage", orange.HierarchicalClustering.Ward),
("Complete linkage", orange.HierarchicalClustering.Complete),
]
root = orange.HierarchicalClustering(matrix, linkage=linkages[linkage][1])
attributes = [x.name for x in matrix.items.domain]
def build_hierarchy(node, root=False):
values_dict = dict([(x,matrix.items[node.first][x].value) for x in attributes]) if not node.branches else {}
for attribute in values_dict.keys():
if type(values_dict[attribute]) == float:
values_dict[attribute]="%.3f" % values_dict[attribute]
return {
'name' : 'root' if root else '',
'id' : node.first if not node.branches else -1,
'height' : node.height if node.branches else 0,
'children' : [build_hierarchy(node.left), build_hierarchy(node.right)] if node.branches else []
'children' : [build_hierarchy(node.left), build_hierarchy(node.right)] if node.branches else [],
'values' : values_dict,
'leaf' : True if not node.branches else False
}
hierarchy = json.dumps(build_hierarchy(root, root=True))
return render(request, 'interactions/cforange_hierarchical_clustering.html', {'widget' : widget, 'hierarchy' : hierarchy})
\ No newline at end of file
return render(request, 'interactions/cforange_hierarchical_clustering.html', {'widget' : widget, 'hierarchy' : hierarchy, 'attributes':attributes})
\ No newline at end of file
......@@ -385,5 +385,5 @@ def cforange_attribute_distance(input_dict):
def cforange_hierarchical_clustering(input_dict):
return {'centroids' : None, 'selected_examples' : None, 'unselected_examples' : None}
def cforange_hierarchical_clustering_finished(input_dict):
def cforange_hierarchical_clustering_finished(postdata,input_dict,output_dict):
return {'centroids' : None, 'selected_examples' : None, 'unselected_examples' : None}
<div id="widgetinteract-{{widget.pk}}" rel="{{widget.pk}}" class="widgetinteractdialog" title="{{widget.name}} interaction" width="1280" height="800">
<div>
<div id="widgetinteract-{{widget.pk}}" rel="{{widget.pk}}" class="widgetinteractdialog" title="{{widget.name}} interaction" width="1050" height="800">
<div id="clustering_canvas{{widget.pk}}"></div>
<form>
<input type="hidden" name="widget_id" value="{{widget.pk}}"/>
<input type="hidden" name="widget_id" value="{{widget.pk}}" style="display:none;" />
<input type="hidden" name="selected_height" value="0" style="display:none;"/>
<input type="hidden" class="selected_nodes{{widget.pk}}" name="selected_nodes" value="" style="display:none;"/>
</form>
</div>
<div>
Display label:
<select class="attributeselect">
<option value="-1">None</option>
{% for attribute in attributes %}
<option value="{{forloop.counter0}}">{{attribute}}</option>
{% endfor %}
</select>
</div>
<div id="legend{{widget.pk}}">
</div>
</div>
<style type="text/css">
path.arc {
cursor: move;
/* cursor: move;*/
fill: #fff;
}
path.selectedarc {
fill: red;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke: black;
stroke-width: 1.5px;
}
......@@ -28,11 +44,21 @@ path.arc {
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
<script type="text/javascript">
var hierarchy = {{hierarchy|safe}};
draw(1280, 800, hierarchy, "#clustering_canvas{{widget.pk}}");
draw(1000, 600, hierarchy, "#clustering_canvas{{widget.pk}}");
function draw(w, h, hierarchy, target) {
var colors = ['red','blue','yellow','green','magenta','cyan','black'];
var attributes = [];
{% for attribute in attributes %}
attributes.push('{{attribute}}');
{% endfor %}
var rx = w/2,
ry = h/2,
m0,
......@@ -50,13 +76,13 @@ function draw(w, h, hierarchy, target) {
.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
var svg = d3.select(target).append("div")
.style("width", w + "px")
.style("height", w + "px")
// .style("width", w + "px")
// .style("height", (h+50) + "px")
.on("click", select_clusters);
var vis = svg.append("svg:svg")
.attr("width", w)
.attr("height", w)
.attr("height", h+50)
.append("svg:g")
.attr("transform", "translate(" + rx + "," + ry + ")");
......@@ -92,37 +118,178 @@ function draw(w, h, hierarchy, target) {
node.append("svg:circle")
.attr("r", 3);
node.append("svg:text")
.attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.text(function(d) { return d.name; });
for (a in attributes) {
node.append("svg:text")
.attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.attr("class", "attribute"+a+" attributes")
.text(function(d) { return d.values[attributes[a]]; });
}
$(".attributes").hide();
$(".attribute"+$(".attributeselect").val()).show();
$(".attributeselect").change(function () {
$(".attributes").hide();
$(".attribute"+$(".attributeselect").val()).show();
})
d3.select(window)
.on("mousemove", mousemove)
.on("mouseup", mouseup);
var cluster_index=0;
var clusters = [];
var counters = [];
function color_nodes(node,treshold,cluster) {
if (node.height<treshold||node.height==0) {
node.cluster = cluster;
if (clusters.indexOf(cluster)==-1) {
clusters.push(cluster);
counters.push(0);
selected_clusters.push(true);
}
for (n in node.children) {
color_nodes(node.children[n],treshold,cluster);
}
} else {
node.cluster = -1;
for (n in node.children) {
color_nodes(node.children[n],treshold,++cluster_index);
}
}
}
hsv2rgb = function(h,s,v) {
var rgb, i, data = [];
if (s === 0) {
rgb = [v,v,v];
} else {
h = h / 60;
i = Math.floor(h);
data = [v*(1-s), v*(1-s*(h-i)), v*(1-s*(1-(h-i)))];
switch(i) {
case 0:
rgb = [v, data[2], data[0]];
break;
case 1:
rgb = [data[1], v, data[0]];
break;
case 2:
rgb = [data[0], v, data[2]];
break;
case 3:
rgb = [data[0], data[1], v];
break;
case 4:
rgb = [data[2], data[0], v];
break;
default:
rgb = [v, data[0], data[1]];
break;
}
}
return '#' + rgb.map(function(x){
return ("0" + Math.round(x*255).toString(16)).slice(-2);
}).join('');
};
var threshold_arc = null;
var root_node = nodes[0];
function display_nodes() {
return_nodes = [];
node.selectAll("circle")
.style('fill', function(d) {
if (d.cluster==-1) {
return 'white';
} else {
if (d.children.length==0) {
counters[clusters.indexOf(d.cluster)]++;
}
var sat = 1;
if (!selected_clusters[clusters.indexOf(d.cluster)]) {
d.selected = false;
sat=0.15
} else {
d.selected = true;
}
return hsv2rgb(((clusters.indexOf(d.cluster)*1.0)/(clusters.length))*360,sat,1);
}
})
.style('stroke', function(d) {
if (d.cluster==-1) {
return 'black';
} else {
var sat = 1;
if (!selected_clusters[clusters.indexOf(d.cluster)]) {
sat=0.15
}
if (d.leaf) {
return_nodes.push([d.id,d.selected,clusters.indexOf(d.cluster)]);
}
return hsv2rgb(((clusters.indexOf(d.cluster)*1.0)/(clusters.length))*360,sat,0.8);
}
});
$(".selected_nodes{{widget.pk}}").val(JSON.stringify(return_nodes));
}
function select_clusters() {
var click_pos = d3.mouse(this);
var r = Math.min(distance(click_pos, root), ry-delta);
var r_dist = distance(click_pos,root)
if (r_dist > ry-delta) {
return false;
}
var r_height = max_height-((distance(click_pos,root)/(ry-delta))*max_height);
if (threshold_arc) {
threshold_arc.remove();
}
threshold_arc = vis.append("svg:path")
.attr("class", "arc")
.attr("class", "selectedarc")
.attr("d", d3.svg.arc().innerRadius(r).outerRadius(r+1).startAngle(0).endAngle(2 * Math.PI));
node.selectAll("circle")
.style('fill', function(d) {
if (distance([d.x, d.y], [root_node.x, root_node.y]) > r) {
return 'blue';
} else {
return 'white';
}
});
cluster_index = 0;
clusters = [];
counters = [];
selected_clusters = [];
color_nodes(nodes[0],r_height,0);
//clusters.sort(function() { return 0.5 - Math.random();});
display_nodes();
var legend = $("#legend{{widget.pk}}");
legend.html("");
for (c in counters) {
legend.append('<span style="display:inline; color:'+hsv2rgb((c*1.0/counters.length)*360,1,1)+'"><input type="checkbox" class="clusterselect" checked="checked" style="display:inline !important; width:20px;" rel="'+c+'" />'+counters[c]+"</span> ");
}
$(".clusterselect").change(function() {
var current_cluster = parseInt($(this).attr('rel'));
if ($(this).attr('checked')=='checked') {
selected_clusters[current_cluster]=true;
} else {
selected_clusters[current_cluster]=false;
}
display_nodes();
});
}
function mouse(e) {
......@@ -130,8 +297,8 @@ function draw(w, h, hierarchy, target) {
}
function mousedown() {
m0 = mouse(d3.event);
d3.event.preventDefault();
/* m0 = mouse(d3.event);
d3.event.preventDefault();*/
}
function mousemove() {
......
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