Skip to content

Instantly share code, notes, and snippets.

@chronick
Last active November 29, 2015 22:02
Show Gist options
  • Save chronick/27419128b43711836fed to your computer and use it in GitHub Desktop.
Save chronick/27419128b43711836fed to your computer and use it in GitHub Desktop.

Some random stuff I was messing around with

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)
.iso.face.bottom {
display: none;
}
.iso.outline {
stroke: white;
fill: none;
}
<!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>
(function() {
var data, enter_pipedons, height, iso_layout, isometric, parallelepipedon, path_generator, pipedons, svg, vis, width, y_color;
// COLORS = ['hsl(10, 60%, 50%)', 'hsl(15, 90%, 60%)', 'hsl(30, 90%, 60%)', 'hsl(48, 99%, 70%)'];
COLORS = ['#24806C', '#30ACB9', '#49C983', '#B8E7B8'];
// COLORS = ['#359447'];
var CELL_SIZE = 30;
var PAD = 1;
var SIZE = 14;
var HEIGHT = 20;
var PASSES = 2;
function numericSort(array) {
return array
// ensure the array is changed in-place
.slice()
// comparator function that treats input as numeric
.sort(function(a, b) {
return a - b;
});
}
function mode(x) {
// Handle edge cases:
// The median of an empty list is null
if (x.length === 0) { return null; }
else if (x.length === 1) { return x[0]; }
// Sorting the array lets us iterate through it below and be sure
// that every time we see a new number it's new and we'll never
// see the same number twice
var sorted = numericSort(x);
// This assumes it is dealing with an array of size > 1, since size
// 0 and 1 are handled immediately. Hence it starts at index 1 in the
// array.
var last = sorted[0],
// store the mode as we find new modes
value,
// store how many times we've seen the mode
maxSeen = 0,
// how many times the current candidate for the mode
// has been seen
seenThis = 1;
// end at sorted.length + 1 to fix the case in which the mode is
// the highest number that occurs in the sequence. the last iteration
// compares sorted[i], which is undefined, to the highest number
// in the series
for (var i = 1; i < sorted.length + 1; i++) {
// we're seeing a new number pass by
if (sorted[i] !== last) {
// the last number is the new mode since we saw it more
// often than the old one
if (seenThis > maxSeen) {
maxSeen = seenThis;
value = last;
}
seenThis = 1;
last = sorted[i];
// if this isn't a new number, it's one more occurrence of
// the potential mode
} else { seenThis++; }
}
return value;
}
svg = d3.select('svg');
width = svg.node().getBoundingClientRect().width;
height = svg.node().getBoundingClientRect().height;
vis = svg.append('g').attr({
transform: "translate(" + (width / 2) + "," + 20 + ")"
});
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]) * 0.85
];
};
parallelepipedon = function(d) {
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;
}
var mrt = isometric([d.x, d.y + d.dy, d.z + d.dz]),
nt = isometric([d.x + d.dx, d.y + d.dy, d.z + d.dz]),
mlt = isometric([d.x + d.dx, d.y, d.z + d.dz]),
ft = isometric([d.x, d.y, d.z + d.dz]),
mrb = isometric([d.x, d.y + d.dy, d.z]),
nb = isometric([d.x + d.dx, d.y + d.dy, d.z]),
mlb = isometric([d.x + d.dx, d.y, d.z]),
fb = isometric([d.x, d.y, d.z]);
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;
}
return data
.map(function(d) {
return shape(d, scale);
})
.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';
};
color = function(d) {
var x = d.x / CELL_SIZE;
var y = d.y / CELL_SIZE;
var what = d3.scale.quantize()
.domain([3,0])
.range(COLORS)(d.c);
return what;
}; //d3.scale.category10();
var colorMarchingSquares = function(e, y, arr) {
return e.map(function(d, x) {
return mode([
arr[(y + 1) % SIZE][x],
arr[(y - 1 + SIZE) % SIZE][x],
arr[y][(x + 1) % SIZE],
arr[y][(x - 1 + SIZE) % SIZE],
arr[(y + 1) % SIZE][(x + 1) % SIZE],
arr[(y + 1) % SIZE][(x - 1 + SIZE) % SIZE],
arr[(y - 1 + SIZE) % SIZE][(x + 1) % SIZE],
arr[(y - 1 + SIZE) % SIZE][(x - 1 + SIZE) % SIZE]
])
})
}
var colorTranslate = function(direction) {
return function(e, y, arr) {
return e.map(function(d, x) {
switch (direction) {
case 'top':
return arr[(y + 1) % SIZE][x];
case 'bottom':
return arr[(y - 1 + SIZE) % SIZE][x];
case 'right':
return arr[y][(x + 1) % SIZE];
case 'left':
return arr[y][(x - 1 + SIZE) % SIZE];
default:
return arr[y][x];
}
})
}
}
var colorData;
// Manual color data
// colorData = [
// [1,1,1,1,2,3,3,3,2,1,1,1,1,1],
// [1,1,1,1,2,3,3,2,1,1,1,1,1,1],
// [1,1,2,2,2,3,3,2,2,1,1,1,1,1],
// [1,1,2,3,3,3,3,3,2,1,1,1,1,1],
// [1,1,2,2,3,2,2,3,2,1,1,1,1,1],
// [1,2,2,2,2,2,2,2,2,2,2,1,1,1],
// [1,1,1,2,3,3,3,3,3,2,2,1,1,1],
// [2,3,3,3,3,3,3,2,3,2,1,1,1,1],
// [1,2,2,3,3,3,2,2,2,1,1,1,1,1],
// [1,2,2,3,3,3,1,2,2,1,1,1,1,1],
// [2,2,2,3,3,3,1,1,1,1,1,1,1,1],
// [2,2,3,3,1,1,1,1,1,1,1,1,1,1],
// [2,2,2,2,2,2,1,1,1,1,1,1,1,1],
// [1,1,1,2,2,2,1,1,1,1,1,1,1,1]
// ];
// var SIZE = colorData.length;
// Randomly generate grid palette values
colorData = d3.range(SIZE).map(function(i) {
return d3.range(SIZE).map(function(j) {
return Math.floor(Math.random() * 4);
});
});
// Pass through marching squares to create continuous clusters
colorData = d3.range(PASSES).reduce(function(accum) {
return accum.map(colorMarchingSquares);
}, colorData)
var fn = function(colorData) {
// Generate data array for path generation
data = d3.range(SIZE * SIZE).map(function(i) {
var x = (i % SIZE);
var y = Math.floor(i / SIZE);
return {
x: x * CELL_SIZE,
y: y * CELL_SIZE,
dx: CELL_SIZE - PAD,
dy: CELL_SIZE - PAD,
c: colorData[y][x],
dz: HEIGHT//10 + Math.random() * SIZE * L
};
});
pipedons = vis.selectAll('.pipedon')
.data(iso_layout(data, parallelepipedon));
pipedons.select('path.iso.face.bottom').attr({
d: function(d) {
return path_generator(d.iso.face_bottom);
}
})
pipedons.select('path.iso.face.top').attr({
d: function(d) {
return path_generator(d.iso.face_top);
},
fill: color
});
pipedons.select('path.iso.face.right').attr({
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);
}
});
pipedons.select('path.iso.face.left').attr({
d: function(d) {
return path_generator(d.iso.face_left);
},
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 - 24);
}
});
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 top template',
d: function(d) {
return path_generator(d.iso.face_top);
},
fill: color
});
enter_pipedons.append('path').attr({
"class": 'iso face left',
d: function(d) {
return path_generator(d.iso.face_left);
},
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 - 24);
}
});
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 outline',
d: function(d) {
return path_generator(d.iso.outline);
}
});
};
fn(colorData);
setInterval(function(){
colorData = colorData
.map(colorTranslate('top'))
// .map(colorMarchingSquares);
fn(colorData);
}, 100)
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment