Skip to content

Instantly share code, notes, and snippets.

@fractalytics
Last active February 19, 2018 00:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fractalytics/03e257578211f095036adb1a7d5c45c0 to your computer and use it in GitHub Desktop.
Save fractalytics/03e257578211f095036adb1a7d5c45c0 to your computer and use it in GitHub Desktop.
Segmentation
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
.tooltip {
background: white none repeat scroll 0% 0%;
border: 1px solid rgb(0, 0, 219);
padding: 5px;
box-shadow: 1px 3px 11px rgb(213, 213, 213);
}
.ttl {
font-size: 11px;
stroke: rgb(59, 51, 51);
stroke-width: 0px;
}
div.body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 410px;
position: absolute;
top: 1px;
}
div.body svg {
background: #fcfcff;
border: 2px solid #c5c5c5;
border-radius: 6.5px;
}
svg {
width: 100%;
height: 100%;
position: center;
}
.toolTip {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: absolute;
display: none;
width: auto;
height: auto;
background: none repeat scroll 0 0 white;
border: 0 none;
border-radius: 20px 8px 8px 8px;
box-shadow: -3px 3px 15px #888888;
color: black;
font: 12px sans-serif;
padding: 5px;
text-align: center;
}
text {
font: 12px sans-serif;
color: white;
}
text.value {
font-size: 100%;
fill: white;
}
.axisHorizontal path {
fill: none;
}
.axisHorizontal .tick line {
stroke-width: 1;
stroke: rgba(0, 0, 0, 0.2);
}
.bar {
fill-opacity: .9;
}
</style>
<body>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="http://d3js.org/d3.v3.min.js" ></script>
<script>
/*cluster params*/
var show_individuals = true
var file = "outfile_order.json"
var rect_clusters = {}
/**/
var margin = {
top: 70,
right: 30,
bottom: 30,
left: 50
},
width = screen.width/1.7 - margin.left - margin.right,
height = screen.height/1.3 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//à voir
function getAvg(array) {
var total_x = 0;
var total_y = 0;
// expected output: 10
for(var i = 0; i < array.length; i++) {
total_x += array[i][0];
total_y += array[i][1];
}
return [total_x/array.length,total_y/array.length]
}
var cluster_data;
var obs_data;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json(file, function(error, data) {
if (error) throw error;
cluster_data = data['features']
obs_data=data['individuals']
data = data['individuals']
console.log(data)
console.log(cluster_data)
data.forEach(function(d) {
d.Dim_1 = +d.Dim_1;
d.Dim_2 = +d.Dim_2;
});
x.domain(d3.extent(data, function(d) {
return d.Dim_1;
})).nice();
y.domain(d3.extent(data, function(d) {
return d.Dim_2;
})).nice();
var nb_cluster=Array.apply(null, Array(cluster_data.length)).map(Number.prototype.valueOf,0);
for (var iter = 0; iter <obs_data.length ; iter++) {
nbc=obs_data[iter]['cluster']-1;
nb_cluster[nbc]=nb_cluster[nbc]+1
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Dim 1");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Dim 2");
var culsters_points = []
for (var i = 1; i <= cluster_data.length; i++) {
var points = []
data.forEach(function(d) {
if (d.cluster == String(i)) {
points.push([x(d.Dim_1), y(d.Dim_2)])
}
});
culsters_points.push(points)
}
var padding = 30
for (var j = 0; j < culsters_points.length; j++) {
d3.select('body')
.append('div')
.attr('class', function(d) {
return "body body-" + (j + 1);
})
.html('<div style="text-align: right;position: relative;font-size: 20px;font-weight: bold"><span style="left: 0;position: absolute;color:#b3b6b7;">Cluster '+ cluster_data[j].cluster +'</span><span style="font-size: 10px;" >'+nb_cluster[j]+'/'+obs_data.length+' '+Math.round(nb_cluster[j]/obs_data.length*100) +'%'+'</span></div>')
$(".body-" + (j + 1)).draggable({
drag: function(event, ui) {
var classId = ui.helper[0].className.charAt(10)
var width = 410;
var centerX = $(ui)[0].offset.left + width / 2;
var centerY = $(ui)[0].offset.top;
svg.select(".line.line-" + classId)
.attr("x2", centerX)
.attr("y2", centerY);
}
});
var centerPoint
if (culsters_points[j].length == 1) {
centerPoint = culsters_points[j][0]
} else if (culsters_points[j].length == 2) {
cx = (culsters_points[j][0][0] + culsters_points[j][1][0]) / 2
cy = (culsters_points[j][0][1] + culsters_points[j][1][1]) / 2
centerPoint = [cx, cy]
} else {
centerPoint = d3.geom.polygon(culsters_points[j]).centroid();
}
var expandedHull = culsters_points[j].map(function(vertex) {
var vector = [vertex[0] - centerPoint[0],
vertex[1] - centerPoint[1]
];
var vectorLength = Math.sqrt(vector[0] * vector[0] +
vector[1] * vector[1]);
var normalizedVector = [vector[0] / vectorLength,
vector[1] / vectorLength
];
return [vertex[0] + normalizedVector[0] * padding,
vertex[1] + normalizedVector[1] * padding
];
});
if (culsters_points[j].length == 1) {
var hull = svg.append("path")
.attr("class", "hull ")
.style("fill", "rgba(200, 119, 180, 0.33)")
.style("stroke", "rgb(181, 210, 230)")
.style("stroke-width", "10px")
.style("opacity", "0.8")
.style("stroke-linejoin", "round")
.on("mouseover", function(d) {
var classId = parseInt(d3.select(this).attr('class').replace('hull hull-', ''));
d3.select("div.body-" + (j + 1))
.style("display", 'block');
createBar(d3.mouse(this)[0], d3.mouse(this)[1], classId);
})
.on("mouseleave", function(d) {
d3.select("div.body-" + (j + 1))
.style("display", 'none');
});
expandedHull = [centerPoint * 2, centerPoint, centerPoint]
hull.datum(d3.geom.hull(expandedHull)).attr("d", function(d) {
return "M" + d.join("L") + "Z";
});
} else if (culsters_points[j].length == 2) {
var hull = svg.append("path")
.attr("class", "hull")
.style("fill", "rgba(31, 119, 180, 0.33)")
.style("stroke", "rgb(181, 210, 230)")
.style("stroke-width", "10px")
.style("opacity", "0.8")
.style("stroke-linejoin", "round")
.on("mouseover", function(d) {
var classId = parseInt(d3.select(this).attr('class').replace('hull hull-', ''));
d3.select("div.body-" + (j + 1))
.style("display", 'block');
createBar(d3.mouse(this)[0], d3.mouse(this)[1], classId);
})
.on("mouseleave", function(d) {
d3.select("div.body-" + (j + 1))
.style("display", 'none');
});
expandedHull.push(centerPoint)
hull.datum(d3.geom.hull(expandedHull)).attr("d", function(d) {
return "M" + d.join("L") + "Z";
});
} else {
var hull = svg.append("path")
.attr("class", "hull hull-" + (j + 1))
.style("fill", color(j + 1))
.style("stroke", color(j + 1))
.style("opacity", 0.3)
.style("stroke-width", "10px")
.style("stroke-linejoin", "round")
.on("mouseover", function(d) {
var classId = parseInt(d3.select(this).attr('class').replace('hull hull-', ''));
var hull_vars = getClusterVars(cluster_data, classId)
var fourPoints = getFourPoints(d)
var coords = ConvertToCoords(whichCoords(fourPoints, hull_vars.length), fourPoints, hull_vars.length)
var c = d3.geom.polygon(d).centroid();
window.firstDrawing = window.firstDrawing || [];
window.firstDrawing[classId] = typeof(window.firstDrawing[classId]) === 'undefined';
if (window.firstDrawing[classId]) {
createBar(coords[0], coords[1], hull_vars, classId);
d3.select("div.body-" + classId).style("display", 'block');
d3.select(".line.line-" + classId).remove();
svg.append("line") // attach a line
.attr("class", "line line-" + classId)
.style("stroke", "#c5c5c5") // colour the line
.style("stroke-width", "3px") // colour the line
// .style("stroke-dasharray","5,5")
.attr("x1", c[0]) // x position of the first end of the line
.attr("y1", c[1]) // y position of the first end of the line
.attr("x2", coords[0] - 36) // x position of the second end of the line
.attr("y2", coords[1] - 30);
}
})
hull.datum(d3.geom.hull(expandedHull)).attr("d", function(d) {
return "M" + d.join("L") + "Z";
});
}
rect_clusters[j + 1] = getFourPoints(expandedHull)
}
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) {
return x(d.Dim_1);
})
.attr("cy", function(d) {
return y(d.Dim_2);
})
.style("fill", function(d) {
return color(d.cluster);
})
.on("mouseover", function(d) {
console.log('over -> cluser point ', culsters_points[parseInt(d.cluster) - 1])
var c = d3.geom.polygon(culsters_points[parseInt(d.cluster) - 1]).centroid()
var cp = culsters_points[parseInt(d.cluster) - 1];
var oc=getAvg(culsters_points[parseInt(d.cluster) - 1])
var vector = [Math.pow(d.Dim_1 - x.invert(oc[0]),2), Math.pow(d.Dim_2 - y.invert(oc[1]),2)];
console.log("centroid = ", [x.invert(c[0]), y.invert(c[1])])
console.log("point = ", [d.Dim_1, d.Dim_2])
console.log("----", vector)
var vectorLength = Math.sqrt(vector[0]+vector[1]);
console.log(vectorLength)
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d.ENTRY + "<br> distance to Centroid : " + vectorLength.toFixed(2))
.style("position", "absolute")
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
if (show_individuals) {
svg.selectAll('.ttl')
.data(data)
.enter().append('svg:text')
.attr("class", "ttl")
.text(function(d) {
return d.ENTRY
})
.attr("x", function(d) {
return x(d.Dim_1) + 5;
})
.attr("y", function(d) {
return y(d.Dim_2) - 10
});
}
});
function smallLabel(label) {
console.log(label.length)
if (label.length > 30) return label.substring(0, 27) + '..'
else return label
}
function createBar(coorX, coorY, hull_vars, classId) {
console.log('start bar creation', coorX, coorY)
data2 = hull_vars
var vars_length = hull_vars.length
console.log(vars_length * 26 + 12)
var axisMargin = 20,
margin = 40,
valueMargin = 4,
width = parseInt(d3.select('div.body-' + classId).style('width'), 9),
height = vars_length * 17 + 12,
barHeight = 17,
barPadding = 30,
data2, bar, svg, scale, xAxis, labelWidth = 0;
var divW = 410
var divH = vars_length * 26 + 12
max = Math.abs(d3.max(data2, function(d) {
return d.value;
}));
console.log("max", max)
svg = d3.select('div.body-' + classId)
.style('top', (coorY) + 'px')
.style('left', (coorX) + 'px')
.append("svg")
.attr("width", width)
.attr("height", height);
bar = svg.selectAll("g")
.data(data2)
.enter()
.append("g");
bar.attr("class", "bar")
.attr("cx", 0)
.attr("transform", function(d, i) {
return "translate(" + 50 + "," + (i * (barHeight + 4) + 10) + ")";
});
bar.append("text")
.attr("class", "label")
.attr("y", barHeight / 2)
.attr("dy", ".35em") //vertical align middle
.text(function(d) {
return smallLabel(d.label);
}).each(function() {
labelWidth = Math.ceil(Math.max(labelWidth, this.getBBox().width));
});
scale = d3.scale.linear()
.domain([0, max])
.range([0, width - 50 - labelWidth]);
xAxis = d3.svg.axis()
.scale(scale)
.tickSize(-height + 2 * margin)
.orient("bottom");
bar.append("rect")
.attr("transform", "translate(" + (labelWidth + 15) + ", 0)")
.attr("height", barHeight)
.attr("ry", 0)
.attr("rx", 4)
.style('fill', function(d) {
console.log(d.value)
return d.value > 0 ? '#008ad6' : '#f25c00'
})
.attr("width", function(d) {
console.log(scale(Math.abs(d.value)))
return scale(Math.abs(d.value));
});
bar.append("text")
.attr("class", "value")
.attr("y", barHeight / 2)
.attr("dx", -valueMargin + labelWidth) //margin right
.attr("dy", "0.35em") //vertical align middle
.attr("text-anchor", "end")
.text(function(d) {
if(String(d.value).length > 3 && d.value > 0 ){
return d.value
}else if (String(d.value).length > 4 && d.value < 0){
return d.value
}else if (String(d.value).length ==1 && d.value > 0){
return (d.value+".00")
}
else if (String(d.value).length ==3 && d.value > 0 ){
return (d.value+"0")
}
else if (String(d.value).length ==4 && d.value < 0 ){
return (d.value+"0")
}
else if (String(d.value).length ==2 && d.value < 0 ){
return (d.value+".00")
}
})
.attr("x", function(d) {
var width = this.getBBox().width;
return 45;
});
}
function getFourPoints(pointsList) {
var minX = 999999;
var maxX = -999999;
var minY = 999999;
var maxY = -999999;
for (var i = 0; i < pointsList.length; i++) {
var x = pointsList[i][0]
var y = pointsList[i][1]
minX = minX < x ? minX : x
maxX = maxX > x ? maxX : x
minY = minY < y ? minY : y
maxY = maxY > y ? maxY : y
}
return [
[minX, minY],
[minX, maxY],
[maxX, minY],
[maxX, maxY]
]
}
var screenWidth = window.width;
var screenHeigth = window.height;
function whichCoords(pointsList, vars_length) {
var wBar = 349;
var hBar = vars_length * 26 + 12;
p1 = pointsList[0]
p2 = pointsList[1]
p3 = pointsList[2]
p4 = pointsList[3]
console.log(p1[1] + ' > ' + hBar + ' + 10 &&' + p1[0] + ' > ' + wBar + ' +10')
if (p1[1] > hBar + 10 && p1[0] > wBar + 10) {
console.log(p1[1] + ' > ' + hBar + ' + 10 &&' + p1[0] + ' > ' + wBar + ' +10')
return "1"
} else if (p1[1] > hBar + 10) {
console.log(p1[1], '>', hBar, '+', 10)
return "2"
} else if (p3[1] > hBar + 10 && (screenWidth - p3[0]) > wBar + 10) {
console.log(p3[1] + '>' + hBar + '+ 10 && (' + screenWidth + '- ' + p3[0] + ') > ' + wBar + ' +10')
return "3"
} else if (((screenWidth - p3[0]) > wBar + 10) && ((p1[1] + hBar) <= screenHeigth)) {
console.log(screenWidth, '- ', p3[0], ' > ', wBar, '+', 10, '&& ', p1[1], '+', hBar, '<=', screenHeigth)
return "4"
} else if (((screenHeigth - p4[0]) > hBar + 10) && (screenWidth - p4[1]) > wBar + 10) {
return "5"
} else if ((screenHeigth - p4[0]) > hBar + 10) {
return "6"
} else if ((p1[0] > hBar + 10) && (screenWidth - p4[1]) > wBar + 10) {
return "7"
} else if ((p1[0] > hBar + 10) && ((p1[0] + hBar) <= screenHeigth)) {
return "8"
} else return "9"
}
function getClusterVars(cluster_data, classId) {
for (var i in cluster_data) {
if (cluster_data[i]['cluster'] == classId) {
return cluster_data[i]['vars']
}
}
}
function ConvertToCoords(str, pointsList, vars_length) {
var wBar = 349;
var hBar = vars_length * 26 + 12;
p1 = pointsList[0]
p2 = pointsList[1]
p3 = pointsList[2]
p4 = pointsList[3]
console.log(str, pointsList)
switch (str) {
case '1':
return [p1[0] - (wBar) + 100, p1[1] - (hBar)]
break;
case '2':
{
console.log(p1, p2, p3, p4)
console.log('[ ' + p1[0] + '+' + 100 + ' ,' + p1[1] + ']')
return [p1[0] + 30, p1[1] - 100]
}
break;
case '3':
{
console.log(p1, p2, p3, p4)
return [p3[0] + 100, p3[1] + 30 - hBar]
}
break;
case '4':
{
console.log(p1, p2, p3, p4)
return [p3[0] + 100, p3[1] + 30]
}
break;
case '5':
return [p4[0] + 100, p4[1] + 30]
break;
case '6':
return [p2[0] + 100, p2[1] + 30]
break;
case '7':
{
console.log(p1, p2, p3, p4)
return [p2[0] - wBar, p2[1] + 30]
}
break;
case '8':
return [p1[0] - (wBar) + 100, p1[1] + 30]
break;
case '9':
return [10, 10]
break;
}
}
</script>
<div class="body"></div>
</body>
{ "features": [{
"cluster": " 1 ",
"vars" : [
{
"label" : " % Environmental disease ",
"value": 2.97
},
{
"label" : " Number of theft ",
"value": 2.57
},
{
"label" : " Infant mortality rate ",
"value": 2.33
},
{
"label" : " Air pollution index ",
"value": 1.99
}
]
} ,
{
"cluster": " 2 ",
"vars" : [
{
"label" : " Unemployment rate ",
"value": 3.11
},
{
"label" : " Number of suicides ",
"value": 2.7
},
{
"label" : " Number of road fatalities ",
"value": 2.5
},
{
"label" : " Per capita income ",
"value": 2.29
},
{
"label" : " Infant mortality rate ",
"value": -2.09
}
]
} ,
{
"cluster": " 3 ",
"vars" : [
{
"label" : " Cost of housing per year ",
"value": 2.02
},
{
"label" : " % Environmental disease ",
"value": -1.98
},
{
"label" : " Number of theft ",
"value": -2.02
},
{
"label" : " % Low household income ",
"value": -2.05
}
]
} ,
{
"cluster": " 4 ",
"vars" : [
{
"label" : " % Low household income ",
"value": 2.34
},
{
"label" : " Cost of housing per year ",
"value": -2.54
}
]
}
], "individuals": [{"ENTRY":"New York","Dim_1":-2.1942,"Dim_2":2.296,"cluster":1},{"ENTRY":"Los Angeles","Dim_1":3.1648,"Dim_2":0.9603,"cluster":2},{"ENTRY":"Chicago","Dim_1":-1.8366,"Dim_2":1.7185,"cluster":1},{"ENTRY":"Philadelphie","Dim_1":-1.3002,"Dim_2":0.9331,"cluster":1},{"ENTRY":"Detroit","Dim_1":-0.173,"Dim_2":1.297,"cluster":1},{"ENTRY":"Boston","Dim_1":-2.439,"Dim_2":-1.9327,"cluster":3},{"ENTRY":"San Francisco","Dim_1":4.0129,"Dim_2":0.7484,"cluster":2},{"ENTRY":"Washington D. C.","Dim_1":-1.8475,"Dim_2":1.2239,"cluster":1},{"ENTRY":"Pittsburgh","Dim_1":0.4166,"Dim_2":-0.6265,"cluster":4},{"ENTRY":"St Louis","Dim_1":0.1358,"Dim_2":0.0243,"cluster":4},{"ENTRY":"Cleveland","Dim_1":-1.917,"Dim_2":-1.2012,"cluster":3},{"ENTRY":"Baltimore","Dim_1":-0.39,"Dim_2":1.6658,"cluster":1},{"ENTRY":"Houston","Dim_1":1.2336,"Dim_2":0.0336,"cluster":4},{"ENTRY":"Minneapolis","Dim_1":0.3523,"Dim_2":-2.2057,"cluster":3},{"ENTRY":"Dallas","Dim_1":1.3065,"Dim_2":-0.9313,"cluster":4},{"ENTRY":"Cincinnati","Dim_1":-0.424,"Dim_2":-1.5231,"cluster":4},{"ENTRY":"Milwaukee","Dim_1":-0.575,"Dim_2":-2.3562,"cluster":3},{"ENTRY":"Seattle","Dim_1":2.474,"Dim_2":-0.1242,"cluster":2}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment