A fancy (and not that useful) visualization of random data in two dimensions as an isometric bar chart. Notice how the phenomenon of occlusion makes part of the dataset invisible.
Last active
September 12, 2017 23:27
-
-
Save nitaku/8f96bf393b94caff688b to your computer and use it in GitHub Desktop.
Isometric projection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
svg = d3.select('svg') | |
width = svg.node().getBoundingClientRect().width | |
height = svg.node().getBoundingClientRect().height | |
vis = svg.append('g') | |
.attr | |
transform: "translate(#{width/2},#{height/2})" | |
# [x, y, z] -> [-Math.sqrt(3)/2*x+Math.sqrt(3)/2*y, 0.5*x+0.5*y-z] | |
isometric = (_3d_p) -> [-Math.sqrt(3)/2*_3d_p[0]+Math.sqrt(3)/2*_3d_p[1], +0.5*_3d_p[0]+0.5*_3d_p[1]-_3d_p[2]] | |
parallelepipedon = (d) -> | |
d.x = 0 if not d.x? | |
d.y = 0 if not d.y? | |
d.z = 0 if not d.z? | |
d.dx = 10 if not d.dx? | |
d.dy = 10 if not d.dy? | |
d.dz = 10 if not d.dz? | |
fb = isometric [d.x, d.y, d.z], | |
mlb = isometric [d.x+d.dx, d.y, d.z], | |
nb = isometric [d.x+d.dx, d.y+d.dy, d.z], | |
mrb = isometric [d.x, d.y+d.dy, d.z], | |
ft = isometric [d.x, d.y, d.z+d.dz], | |
mlt = isometric [d.x+d.dx, d.y, d.z+d.dz], | |
nt = isometric [d.x+d.dx, d.y+d.dy, d.z+d.dz], | |
mrt = isometric [d.x, d.y+d.dy, d.z+d.dz] | |
d.iso = { | |
face_bottom: [fb, mrb, nb, mlb], | |
face_left: [mlb, mlt, nt, nb], | |
face_right: [nt, mrt, mrb, nb], | |
face_top: [ft, mrt, nt, mlt], | |
outline: [ft, mrt, mrb, nb, mlb, mlt] | |
far_point: fb # used to control the z-index of iso objects | |
} | |
return d | |
iso_layout = (data, shape, scale) -> | |
scale = 1 if not scale? | |
data.forEach (d) -> | |
shape(d, scale) | |
# data must be drawn from farthest to nearest | |
data.sort (a,b) -> a.iso.far_point[1] - b.iso.far_point[1] | |
path_generator = (d) -> 'M' + d.map((p)->p.join(' ')).join('L') + 'z' | |
y_color = d3.scale.category10() | |
L = 30 | |
PAD = 6 | |
data = d3.range(6*6).map (i) -> { | |
x: (i%6)*L, | |
y: Math.floor(i/6)*L, | |
dx: L-PAD, | |
dy: L-PAD, | |
dz: 10+Math.random()*6*L | |
} | |
iso_layout(data, parallelepipedon) | |
pipedons = vis.selectAll('.pipedon') | |
.data(data) | |
enter_pipedons = pipedons.enter().append('g') | |
.attr | |
class: 'pipedon' | |
enter_pipedons.append('path') | |
.attr | |
class: 'iso face bottom' | |
d: (d) -> path_generator(d.iso.face_bottom) | |
enter_pipedons.append('path') | |
.attr | |
class: 'iso face left template' | |
d: (d) -> path_generator(d.iso.face_left) | |
fill: (d) -> | |
# color the template face according to y | |
return y_color(d.y) | |
enter_pipedons.append('path') | |
.attr | |
class: 'iso face right' | |
d: (d) -> path_generator(d.iso.face_right) | |
fill: (d) -> | |
# right face is darker than the template (left face) | |
color = d3.hcl(d3.select(this.parentNode).select('.template').style('fill')) | |
return d3.hcl(color.h, color.c, color.l-12) | |
enter_pipedons.append('path') | |
.attr | |
class: 'iso face top' | |
d: (d) -> path_generator(d.iso.face_top) | |
fill: (d) -> | |
# right face is brighter than the template (left face) | |
color = d3.hcl(d3.select(this.parentNode).select('.template').style('fill')) | |
return d3.hcl(color.h, color.c, color.l+12) | |
enter_pipedons.append('path') | |
.attr | |
class: 'iso outline' | |
d: (d) -> path_generator(d.iso.outline) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.iso.face.bottom { | |
display: none; | |
} | |
.iso.outline { | |
stroke: white; | |
fill: none; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Isometric Projection</title> | |
<link type="text/css" href="index.css" rel="stylesheet"/> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
</head> | |
<body> | |
<svg width="960px" height="500px"></svg> | |
<script src="index.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.4.0 | |
(function() { | |
var L, PAD, data, enter_pipedons, height, iso_layout, isometric, parallelepipedon, path_generator, pipedons, svg, vis, width, y_color; | |
svg = d3.select('svg'); | |
width = svg.node().getBoundingClientRect().width; | |
height = svg.node().getBoundingClientRect().height; | |
vis = svg.append('g').attr({ | |
transform: "translate(" + (width / 2) + "," + (height / 2) + ")" | |
}); | |
isometric = function(_3d_p) { | |
return [-Math.sqrt(3) / 2 * _3d_p[0] + Math.sqrt(3) / 2 * _3d_p[1], +0.5 * _3d_p[0] + 0.5 * _3d_p[1] - _3d_p[2]]; | |
}; | |
parallelepipedon = function(d) { | |
var fb, ft, mlb, mlt, mrb, mrt, nb, nt; | |
if (!(d.x != null)) { | |
d.x = 0; | |
} | |
if (!(d.y != null)) { | |
d.y = 0; | |
} | |
if (!(d.z != null)) { | |
d.z = 0; | |
} | |
if (!(d.dx != null)) { | |
d.dx = 10; | |
} | |
if (!(d.dy != null)) { | |
d.dy = 10; | |
} | |
if (!(d.dz != null)) { | |
d.dz = 10; | |
} | |
fb = isometric([d.x, d.y, d.z], mlb = isometric([d.x + d.dx, d.y, d.z], nb = isometric([d.x + d.dx, d.y + d.dy, d.z], mrb = isometric([d.x, d.y + d.dy, d.z], ft = isometric([d.x, d.y, d.z + d.dz], mlt = isometric([d.x + d.dx, d.y, d.z + d.dz], nt = isometric([d.x + d.dx, d.y + d.dy, d.z + d.dz], mrt = isometric([d.x, d.y + d.dy, d.z + d.dz])))))))); | |
d.iso = { | |
face_bottom: [fb, mrb, nb, mlb], | |
face_left: [mlb, mlt, nt, nb], | |
face_right: [nt, mrt, mrb, nb], | |
face_top: [ft, mrt, nt, mlt], | |
outline: [ft, mrt, mrb, nb, mlb, mlt], | |
far_point: fb | |
}; | |
return d; | |
}; | |
iso_layout = function(data, shape, scale) { | |
if (!(scale != null)) { | |
scale = 1; | |
} | |
data.forEach(function(d) { | |
return shape(d, scale); | |
}); | |
return data.sort(function(a, b) { | |
return a.iso.far_point[1] - b.iso.far_point[1]; | |
}); | |
}; | |
path_generator = function(d) { | |
return 'M' + d.map(function(p) { | |
return p.join(' '); | |
}).join('L') + 'z'; | |
}; | |
y_color = d3.scale.category10(); | |
L = 30; | |
PAD = 6; | |
data = d3.range(6 * 6).map(function(i) { | |
return { | |
x: (i % 6) * L, | |
y: Math.floor(i / 6) * L, | |
dx: L - PAD, | |
dy: L - PAD, | |
dz: 10 + Math.random() * 6 * L | |
}; | |
}); | |
iso_layout(data, parallelepipedon); | |
pipedons = vis.selectAll('.pipedon').data(data); | |
enter_pipedons = pipedons.enter().append('g').attr({ | |
"class": 'pipedon' | |
}); | |
enter_pipedons.append('path').attr({ | |
"class": 'iso face bottom', | |
d: function(d) { | |
return path_generator(d.iso.face_bottom); | |
} | |
}); | |
enter_pipedons.append('path').attr({ | |
"class": 'iso face left template', | |
d: function(d) { | |
return path_generator(d.iso.face_left); | |
}, | |
fill: function(d) { | |
return y_color(d.y); | |
} | |
}); | |
enter_pipedons.append('path').attr({ | |
"class": 'iso face right', | |
d: function(d) { | |
return path_generator(d.iso.face_right); | |
}, | |
fill: function(d) { | |
var color; | |
color = d3.hcl(d3.select(this.parentNode).select('.template').style('fill')); | |
return d3.hcl(color.h, color.c, color.l - 12); | |
} | |
}); | |
enter_pipedons.append('path').attr({ | |
"class": 'iso face top', | |
d: function(d) { | |
return path_generator(d.iso.face_top); | |
}, | |
fill: function(d) { | |
var color; | |
color = d3.hcl(d3.select(this.parentNode).select('.template').style('fill')); | |
return d3.hcl(color.h, color.c, color.l + 12); | |
} | |
}); | |
enter_pipedons.append('path').attr({ | |
"class": 'iso outline', | |
d: function(d) { | |
return path_generator(d.iso.outline); | |
} | |
}); | |
}).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment