Skip to content

Instantly share code, notes, and snippets.

@pm5
Created August 16, 2014 08:23
Show Gist options
  • Save pm5/77bc0363b89e0588676b to your computer and use it in GitHub Desktop.
Save pm5/77bc0363b89e0588676b to your computer and use it in GitHub Desktop.
Drawing Chernoff faces

Drawing Chernoff faces

(function() {
function sign(num) {
if(num > 0) {
return 1;
} else if(num < 0) {
return -1;
} else {
return 0;
}
}
// Implements Chernoff faces (http://en.wikipedia.org/wiki/Chernoff_face).
// Exposes 8 parameters through functons to control the facial expression.
// face -- shape of the face {0..1}
// hair -- shape of the hair {-1..1}
// mouth -- shape of the mouth {-1..1}
// noseh -- height of the nose {0..1}
// nosew -- width of the nose {0..1}
// eyeh -- height of the eyes {0..1}
// eyew -- width of the eyes {0..1}
// brow -- slant of the brows {-1..1}
function d3_chernoff() {
var facef = 0.5, // 0 - 1
hairf = 0, // -1 - 1
mouthf = 0, // -1 - 1
nosehf = 0.5, // 0 - 1
nosewf = 0.5, // 0 - 1
eyehf = 0.5, // 0 - 1
eyewf = 0.5, // 0 - 1
browf = 0, // -1 - 1
line = d3.svg.line()
.interpolate("cardinal-closed")
.x(function(d) { return d.x; })
.y(function(d) { return d.y; }),
bline = d3.svg.line()
.interpolate("basis-closed")
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
function chernoff(a) {
if(a instanceof Array) {
a.each(__chernoff);
} else {
d3.select(this).each(__chernoff);
}
}
function __chernoff(d) {
var ele = d3.select(this),
facevar = (typeof(facef) === "function" ? facef(d) : facef) * 30,
hairvar = (typeof(hairf) === "function" ? hairf(d) : hairf) * 80,
mouthvar = (typeof(mouthf) === "function" ? mouthf(d) : mouthf) * 7,
nosehvar = (typeof(nosehf) === "function" ? nosehf(d) : nosehf) * 10,
nosewvar = (typeof(nosewf) === "function" ? nosewf(d) : nosewf) * 10,
eyehvar = (typeof(eyehf) === "function" ? eyehf(d) : eyehf) * 10,
eyewvar = (typeof(eyewf) === "function" ? eyewf(d) : eyewf) * 10,
browvar = (typeof(browf) === "function" ? browf(d) : browf) * 3;
var face = [{x: 70, y: 60}, {x: 120, y: 80},
{x: 120-facevar, y: 110}, {x: 120-facevar, y: 160},
{x: 20+facevar, y: 160}, {x: 20+facevar, y: 110},
{x: 20, y: 80}];
ele.selectAll("path.face").data([face]).enter()
.append("path")
.attr("class", "face")
.attr("d", bline);
var hair = [{x: 70, y: 60}, {x: 120, y: 80},
{x: 140, y: 45-hairvar}, {x: 120, y: 45},
{x: 70, y: 30}, {x: 20, y: 45},
{x: 0, y: 45-hairvar}, {x: 20, y: 80}];
ele.selectAll("path.hair").data([hair]).enter()
.append("path")
.attr("class", "hair")
.attr("d", bline);
var mouth = [{x: 70, y: 130+mouthvar},
{x: 110-facevar, y: 135-mouthvar},
{x: 70, y: 140+mouthvar},
{x: 30+facevar, y: 135-mouthvar}];
ele.selectAll("path.mouth").data([mouth]).enter()
.append("path")
.attr("class", "mouth")
.attr("d", line);
var nose = [{x: 70, y: 110-nosehvar},
{x: 70+nosewvar, y: 110+nosehvar},
{x: 70-nosewvar, y: 110+nosehvar}];
ele.selectAll("path.nose").data([nose]).enter()
.append("path")
.attr("class", "nose")
.attr("d", line);
var leye = [{x: 55, y: 90-eyehvar}, {x: 55+eyewvar, y: 90},
{x: 55, y: 90+eyehvar}, {x: 55-eyewvar, y: 90}];
var reye = [{x: 85, y: 90-eyehvar}, {x: 85+eyewvar, y: 90},
{x: 85, y: 90+eyehvar}, {x: 85-eyewvar, y: 90}];
ele.selectAll("path.leye").data([leye]).enter()
.append("path")
.attr("class", "leye")
.attr("d", bline);
ele.selectAll("path.reye").data([reye]).enter()
.append("path")
.attr("class", "reye")
.attr("d", bline);
ele.append("path")
.attr("class", "lbrow")
.attr("d", "M" + (55-eyewvar/1.7-sign(browvar)) + "," +
(87-eyehvar+browvar) + " " +
(55+eyewvar/1.7-sign(browvar)) + "," +
(87-eyehvar-browvar));
ele.append("path")
.attr("class", "rbrow")
.attr("d", "M" + (85-eyewvar/1.7+sign(browvar)) + "," +
(87-eyehvar-browvar) + " " +
(85+eyewvar/1.7+sign(browvar)) + "," +
(87-eyehvar+browvar));
}
chernoff.face = function(x) {
if(!arguments.length) return facef;
facef = x;
return chernoff;
};
chernoff.hair = function(x) {
if(!arguments.length) return hairf;
hairf = x;
return chernoff;
};
chernoff.mouth = function(x) {
if(!arguments.length) return mouthf;
mouthf = x;
return chernoff;
};
chernoff.noseh = function(x) {
if(!arguments.length) return nosehf;
nosehf = x;
return chernoff;
};
chernoff.nosew = function(x) {
if(!arguments.length) return nosewf;
nosewf = x;
return chernoff;
};
chernoff.eyeh = function(x) {
if(!arguments.length) return eyehf;
eyehf = x;
return chernoff;
};
chernoff.eyew = function(x) {
if(!arguments.length) return eyewf;
eyewf = x;
return chernoff;
};
chernoff.brow = function(x) {
if(!arguments.length) return browf;
browf = x;
return chernoff;
};
return chernoff;
}
d3.chernoff = function() {
return d3_chernoff(Object);
};
})();
<!DOCTYPE html>
<html>
<head>
<title>Drawing Chernoff faces</title><script src="//d3js.org/d3.v3.min.js"></script>
<script src="chernoff.js"></script>
</head>
<body>
<div id="face"></div>
<div id="controller">
<div class="slider"><label>Face shape <input type="range" name="f" min="0" max="1" step="0.01" value="0"/></label><span class="gauge"></span></div>
<div class="slider"><label>Hair shape <input type="range" name="h" min="-1" max="1" step="0.01" value="0"/></label><span class="gauge"></span></div>
<div class="slider"><label>Mouth shape <input type="range" name="m" min="-1" max="1" step="0.01" value="0"/></label><span class="gauge"></span></div>
<div class="slider"><label>Nose height <input type="range" name="nh" min="0" max="1" step="0.01" value="0"/></label><span class="gauge"></span></div>
<div class="slider"><label>Nose width <input type="range" name="nw" min="0" max="1" step="0.01" value="0"/></label><span class="gauge"></span></div>
<div class="slider"><label>Eyes height <input type="range" name="eh" min="0" max="1" step="0.01" value="0"/></label><span class="gauge"></span></div>
<div class="slider"><label>Eyes width <input type="range" name="ew" min="0" max="1" step="0.01" value="0"/></label><span class="gauge"></span></div>
<div class="slider"><label>Brow slant <input type="range" name="b" min="-1" max="1" step="0.01" value="0"/></label><span class="gauge"></span></div>
</div>
<script>
(function () {
function chernoffFace() {
var width = 500,
height = 200;
var chernoff = d3.chernoff()
.face(function(d) { return d.f; })
.hair(function(d) { return d.h; })
.mouth(function(d) { return d.m; })
.nosew(function(d) { return d.nw; })
.noseh(function(d) { return d.nh; })
.eyew(function(d) { return d.ew; })
.eyeh(function(d) { return d.eh; })
.brow(function(d) { return d.b; });
function gauge(elem) {
return d3.select(elem.parentNode.parentNode).select('.gauge')
}
function control() {
return d3.select("#controller")
}
function data() {
var d = {};
control()
.selectAll('input[type="range"]')
.each(function () {
d[this.name] = this.value;
});
return [d];
}
function drawFace(selection) {
var svg = selection.append("svg")
.attr("width", width)
.attr("height", height);
var face = svg.selectAll("g.chernoff")
.data(data())
.enter().append("g")
.attr("class", "chernoff")
.call(chernoff);
}
function updateFace(selection) {
selection.select("svg")
.selectAll("g.chernoff")
.remove();
// chernoff will not draw on update selections
selection.select("svg")
.selectAll("g.chernoff")
.data(data())
.enter().append("g")
.attr("class", "chernoff")
.call(chernoff);
}
function bindControl(selection) {
control()
.selectAll('input[type="range"]')
.each(function () {
gauge(this)
.text(this.value);
})
.on('input', function () {
gauge(this)
.text(this.value);
selection.call(updateFace);
});
}
function draw(selection) {
selection
.call(drawFace)
.call(bindControl);
}
return draw;
}
d3.select("#face")
.call(chernoffFace());
})();
</script>
<style>
.chernoff > * {
fill: none;
stroke: #000;
}
</style>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment