Skip to content

Instantly share code, notes, and snippets.

@nswamy14
Last active January 7, 2024 21:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nswamy14/e28ec2c438e9e8bd302f to your computer and use it in GitHub Desktop.
Save nswamy14/e28ec2c438e9e8bd302f to your computer and use it in GitHub Desktop.
ClusterPurityVisualizer

Cluster Purity Visualizer

Used to visualize the purity of a cluster. Every circle represents a cluster, each cluster will have n-number of class elements. If a cluster has only single class elements, then its a pure cluster.

Analyzed based on verying length or varying angle. Click on a circle to view and also legend interaction is enabled.

By Default clusters will be sorted in decreasing order.

Module is: Highly reusable, Easily to configure/enhance, Auto rescale when window resizes, Interactive legend.

//Developer: Narayana Swamy
//emailID: narayanaswamy14@gmail.com
d3 = d3 || {};
(function(){
"use strict";
d3.clusterpuritychart = function(){
var containerID,
width,
height,
originalData,
ParentG,LegendG,legendFilter= [],
circleR = 0,
innerRadius =0,eleInRow,noOfCategories,fixAngleLayout,
colorScale = d3.scale.category10(),
radiusScale = d3.scale.linear(),
legendObj = d3.legend(),
scale_temp = d3.scale.linear(),
originalData2,
MinMaxTotal = 0,
margin = {top:20},
totalCount = 0,
colorMap = {};
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.ele;
});
var custLayout = d3.custumPieLayout()
.sort(null)
.value(function(d) {
return d.ele;
});
var arc = d3.svg.arc()
.innerRadius(innerRadius);
var chart = function(selection){
if(fixAngleLayout)
custLayout.fixAngle((360/(noOfCategories- legendFilter.length))/(360/(2*Math.PI)));
width = parseInt(d3.select('#'+containerID).style('width'));
height = parseInt(d3.select('#'+containerID).style('height'));
legendObj.legendOnclickMthod(chart.legendFilter);
selection.each(function(data){
originalData = data;
data.forEach(function(d){
d.values.forEach(function(e){
e.total = d.total;
e.color = colorScale(e.type);
if(!colorMap.hasOwnProperty(e.type))
colorMap[e.type] = {'key':e.type,'color':e.color};
})
});
var length = data.length, eleInRow = Math.ceil(Math.sqrt(length));
var container = d3.select(this);
width = width - 100;
var temp_width = data.length > eleInRow?width-0.5*(width/eleInRow):width;
circleR = d3.min([temp_width,(height-margin.top)])/eleInRow;
var x_offset = 0,y_offset=0;
radiusScale
.domain([0,d3.extent(data,function(d){return d.total})[1]])
.range([(circleR/2)*(1/data.length),circleR/2]);
MinMaxTotal = radiusScale.domain();
container.attr('width',(circleR*eleInRow+0.5*circleR)+200)
.attr('height',circleR*eleInRow + margin.top)
if(!ParentG){
ParentG = container.append('g').attr('class','parentG')
.attr('transform','translate('+(data.length > eleInRow?0.5*circleR:0)+','+margin.top+')');
LegendG = container.append('g').attr('class','legendG');
}
LegendG.attr('transform','translate('+(width+50)+','+((circleR*eleInRow/2)-(Object.keys(colorMap).length*20)/2)+')')
.datum(colorMap)
.call(legendObj);
var circleG = ParentG
.attr('transform','translate('+(data.length > eleInRow?0.5*circleR:0)+','+margin.top+')')
.selectAll('.circleG')
.data(data
.sort(function(a,b){
if(a.total < b.total)
return 1;
else if(a.total > b.total)
return -1;
else
return 0;
})
);
circleG.enter()
.append('g')
.attr('class','circleG')
.attr('transform',function(d,i){
return 'translate('+((i%eleInRow)*(circleR))+','+(Math.floor(i/eleInRow)*circleR)+')';
})
.append('circle')
.attr('r',0)
.attr('cx',circleR/2)
.attr('cy',circleR/2)
.style('fill','white')
.style('stroke','black')
.style('stroke-width','1px');
circleG.exit().transition().duration(0).remove();
circleG
.attr('transform',function(d,i){
if(Math.floor(i/eleInRow)%2 === 1){
x_offset = 0.5*circleR;
y_offset = 0.13*circleR*Math.floor(i/eleInRow);
}
else{
x_offset = 0;
if(Math.floor(i/eleInRow)===0)
y_offset = 0;
else
y_offset = 0.13*circleR*Math.floor(i/eleInRow);
}
return 'translate('+(((i%eleInRow)*(circleR))-x_offset)+','+((Math.floor(i/eleInRow)*circleR)-y_offset)+')';
});
circleG
.selectAll('circle')
.attr('r',0)
.attr('cx',circleR/2)
.attr('cy',circleR/2)
.on('mouseover',function(d){
d3.select(this).style('stroke-width','2px')
})
.on('mouseout',function(d){
d3.select(this).style('stroke-width','1px')
})
.on('click',function(d){
d3.select(this).style('stroke-width','1px')
if(originalData.length == 1)
d3.select('#'+containerID).select('svg').datum(originalData2).call(chart);
else{
originalData2 = originalData;
d3.select('#'+containerID).select('svg').datum([d]).call(chart);
}
})
.transition()
.duration(1000)
.attr('r',circleR/2);
circleG.each(chart.renderPieChart);
})
d3.select(window).on('resize.'+containerID, chart.resize)
};
chart.containerID = function(_){
containerID = _;
return chart;
};
chart.noOfCategories= function(_){
noOfCategories = _;
return chart;
};
chart.fixAngleLayout = function(_){
fixAngleLayout = _;
return chart;
};
chart.legendFilter = function(_){
if(legendFilter.indexOf(_) == -1)
legendFilter.push(_);
else
legendFilter.splice(legendFilter.indexOf(_),1);
d3.select('#'+containerID).select('svg').datum(originalData).call(chart);
return chart;
};
chart.change = function(_){
chart.fixAngleLayout(_);
d3.select('#'+containerID).select('svg').datum(originalData).call(chart);
return chart;
};
chart.resize = function(){
d3.select('#'+containerID).select('svg').datum(originalData).call(chart);
};
chart.renderPieChart = function(data){
scale_temp
.domain(d3.extent(data.values,function(d){return d.ele;}))
.range([parseInt(radiusScale(data.total)*0.2),parseInt(radiusScale(data.total))*(originalData.length==1?0.8:1.2)]);
arc.outerRadius(function (d) {
if(fixAngleLayout){
return scale_temp(d.value);
}
else{
return (radiusScale(d.data.total))*0.8;
}
});
var newData = data.values
.filter(function(d){
if(legendFilter.indexOf(d.type) === -1)
return true;
else
return false;
});
var paths = d3.select(this)
.selectAll('path')
.data(fixAngleLayout?custLayout(newData):pie(newData));
paths.enter()
.append("path")
.attr('transform',function(d,i){
return 'translate('+(circleR/2)+','+(circleR/2)+')';
})
.attr("fill", function(d){
return d.data.color;
})
.attr("stroke", "gray")
.attr("class", "outlineArc")
.attr("d", arc)
.on('click',function(d){
console.log(originalData)
if(originalData.length == 1)
d3.select('#'+containerID).select('svg').datum(originalData2).call(chart);
else{
originalData2 = originalData;
d3.select('#'+containerID).select('svg').datum([d.parentData]).call(chart);
}
})
.each(function(d) { this._current = d;
d.parentData = data; });
paths.exit().transition().duration(1000).remove();
paths
.attr('transform',function(d,i){
return 'translate('+(circleR/2)+','+(circleR/2)+')';
})
.attr("fill", function(d){
return d.data.color;
})
.transition()
.duration(1000)
.attrTween("d", arcTween)
.each('end',function(d) { this._current = d;
d.parentData = data; });
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(10);
return function(t) {
return arc(i(t));
};
}
}
return chart;
};
d3.custumPieLayout = function(){
var sort,fixAngle,
valueFun,
sortcomparator,
padAngle=0;
var pieLayoutObj = function(startAngle,endAngle,padAngle,value,data){
this.startAngle =startAngle;
this.endAngle =endAngle;
this.padAngle = padAngle;
this.value = value;
this.data = data;
};
var layout = function(data){
if(sortcomparator)
data.sort(sortcomparator);
var previousEndAngle = 0,
outputObj = [];
data.forEach(function(d){
outputObj.push(new pieLayoutObj(previousEndAngle,previousEndAngle+fixAngle,padAngle,valueFun(d),d));
previousEndAngle = previousEndAngle+fixAngle;
});
return outputObj;
};
layout.sort = function(_){
//if(typeof _ !== 'function') return;
sortcomparator = _;
return layout;
}
layout.fixAngle = function(_){
if(!arguments.length) return;
fixAngle = _;
return layout;
}
layout.value = function(_){
if(typeof _ !== 'function') return;
valueFun = _;
return layout;
}
layout.padAngle = function(_){
padAngle = _;
return layout;
}
return layout;
};
d3.legend = function () {
var legendOnclickMthod,
legendOnHoverMthod,
legendOnmouseHoverMthod,
legendOnmouseOutMthod,
legendOndblclickMthod;
var chart = function(selection){
selection.each(function(data){
var legend_container = d3.select(this);
var wrap = legend_container.selectAll('.legendChart').data([data]);
var wrapEnter = wrap.enter().append('g').attr('class', 'legendChart');
var gEnter = wrapEnter.append('g');
var g = wrap.select('g')
.attr('transform','translate(10,30)');
wrapEnter.append('text').attr('x',0).attr('y',0).text('Interactive legend')
var g_ci = g.selectAll('circle')
.data(function(d){
return Object.keys(d)
});
g_ci.exit().transition().remove();
g_ci.enter()
.append('circle')
.attr('r',function(d){return 10;})
.style('fill', function(d){return data[d].color})
.style('opacity',1)
.style('stroke', function(d){return data[d].color})
.style('stroke-width',2)
.attr('cx',function(d,i){
return 0;
})
.attr('cy',function(d,i){
return Math.floor(i)*30;
})
.on('mouseout',function(d){
d3.select(this).style('stroke-width',2)
.style('stroke', function(d){return data[d].color});
})
.on('mouseover',function(d){
d3.select(this).style('stroke-width',3)
.style('stroke', function(d){return 'black'});
})
.on('click',function(d){
if(d3.select(this).classed("disable_")){
d3.select(this).style("fill",data[d].color)
.classed("disable_",false);
legendOnclickMthod(data[d].key,true);
}
else
{
d3.select(this).classed("disable_",true)
.style("fill",'white');
legendOnclickMthod(data[d].key,false);
}
});
g_ci.style('fill', function(d){
if(d3.select(this).style('fill')=='rgb(255, 255, 255)')
return 'white';
else
return data[d].color;
})
.style('stroke', function(d){return data[d].color})
.transition()
.duration(1000)
.attr('cx',function(d,i){
return 0;
})
.attr('cy',function(d,i){
return Math.floor(i)*30;
});
var g_txt = g.selectAll('text')
.data(function(d){ return Object.keys(d)});
g_txt.exit().transition().remove();
g_txt.enter()
.append('text');
g_txt.text(function(d){return data[d].key.substring(0,10)})
.attr('font-size','18px')
.transition()
.duration(1000)
.attr('x',function(d,i){
return 30;
})
.attr('y',function(d,i){
return i*30+5;
})
});
//return chart;
};
chart.legendOnclickMthod = function(_){
if(!arguments.length) return legendOnclickMthod;
if(typeof _ != 'function') return legendOnclickMthod;
legendOnclickMthod = _;
return chart;
};
chart.legendOndblclickMthod = function(_){
if(!arguments.length) return legendOndblclickMthod;
if(typeof _ != 'function') return legendOndblclickMthod;
legendOndblclickMthod = _;
return chart;
};
chart.legendOnmouseOutMthod = function(_){
if(!arguments.length) return legendOnmouseOutMthod;
if(typeof _ != 'function') return legendOnmouseOutMthod;
legendOnmouseOutMthod = _;
return chart;
};
chart.legendOnmouseHoverMthod = function(){
if(!arguments.length) return legendOnmouseHoverMthod;
if(typeof _ != 'function') return legendOnmouseHoverMthod;
legendOnmouseHoverMthod = _;
return chart;
};
return chart;
};
})()
[{
"values":[
{
"ele":20,
"type":"c1"
},
{
"ele":40,
"type":"c2"
},
{
"ele":80,
"type":"c3"
},
{
"ele":10,
"type":"c4"
}],
"total": 150
},
{
"values":[
{
"ele":50,
"type":"c1"
},
{
"ele":0,
"type":"c2"
},
{
"ele":0,
"type":"c3"
},
{
"ele":10,
"type":"c4"
}],
"total": 60
},
{
"values":[
{
"ele":25,
"type":"c1"
},
{
"ele":20,
"type":"c2"
},
{
"ele":25,
"type":"c3"
},
{
"ele":60,
"type":"c4"
}],
"total": 130
},
{
"values":[
{
"ele":25,
"type":"c1"
},
{
"ele":25,
"type":"c2"
},
{
"ele":25,
"type":"c3"
},
{
"ele":25,
"type":"c4"
}],
"total": 100
},
{
"values":[
{
"ele":30,
"type":"c1"
},
{
"ele":60,
"type":"c2"
},
{
"ele":20,
"type":"c3"
},
{
"ele":90,
"type":"c4"
}],
"total": 190
},
{
"values":[
{
"ele":10,
"type":"c1"
},
{
"ele":20,
"type":"c2"
},
{
"ele":30,
"type":"c3"
},
{
"ele":40,
"type":"c4"
}],
"total": 100
},
{
"values":[
{
"ele":20,
"type":"c1"
},
{
"ele":60,
"type":"c2"
},
{
"ele":30,
"type":"c3"
},
{
"ele":40,
"type":"c4"
}],
"total": 150
},
{
"values":[
{
"ele":30,
"type":"c1"
},
{
"ele":40,
"type":"c2"
},
{
"ele":50,
"type":"c3"
},
{
"ele":60,
"type":"c4"
}],
"total": 180
},
{
"values":[
{
"ele":30,
"type":"c1"
},
{
"ele":40,
"type":"c2"
},
{
"ele":50,
"type":"c3"
},
{
"ele":60,
"type":"c4"
}],
"total": 180
}]
<!DOCTYPE html>
<!-- COLORS ARE NOT PERFECT YET. TOOLTIP NEEDS WORK-->
<meta charset="utf-8">
<head>
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
#chart{
text-align: center;
width: 60%;
height: 700px;
}
</style>
</head>
<body>
<input id="click_1" type="radio" name="sel" value="true" checked="true">Length representation</input>
<input id="click_2" type="radio" name="sel" value="false">Angle representation</input>
<div id='chart' ></div>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="clusterpurityChart.js"></script>
<script>
var obj;
d3.json('clusterpurityDataSet.json',function(error,data){
obj = d3.clusterpuritychart().containerID('chart').noOfCategories(4).fixAngleLayout(true);
d3.select('#chart').append('svg').datum(data).call(obj);
});
d3.select('#click_1').on('click',clickFun);
d3.select('#click_2').on('click',clickFun);
function clickFun(){
obj.change(document.getElementById('click_1').checked);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment