Skip to content

Instantly share code, notes, and snippets.

@armollica
Last active December 6, 2017 15:50
Show Gist options
  • Save armollica/ba4b60e7fafdcaa6df3fdf88b9c9dc35 to your computer and use it in GitHub Desktop.
Save armollica/ba4b60e7fafdcaa6df3fdf88b9c9dc35 to your computer and use it in GitHub Desktop.
Isometric Projection
<html>
<head>
<style>
html, body {
font-family: monospace;
}
.cube .side {
fill-opacity: 0.4;
stroke: #666;
stroke-width: 0.5;
}
.top { fill: steelblue; }
.bottom { fill: yellow; }
.left { fill: brown; }
.right { fill: forestgreen; }
.axis {
fill: none;
stroke: #000;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var width = 960,
height = 500;
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var projection = isometricProjection();
var path = isometricPath()
.projection(projection);
svg.append('defs').append('marker')
.attr('id', 'arrow')
.attr('markerWidth', 10)
.attr('markerHeight', 10)
.attr('refX', 0)
.attr('refY', 3)
.attr('orient', 'auto')
.attr('markerUnits', 'strokeWidth')
.append('path')
.attr('d', 'M0,0L0,6L9,3Z');
var g = svg.append('g')
.attr('transform', function(d) {
return 'translate(' + (width / 2) + ',' + (height / 2) + ')';
});
var axes = g.append('g').attr('class', 'axes');
var axis = axes.selectAll('.axis').data(axesData())
.enter().append('g')
.attr('class', 'axis');
axis.append('path')
.attr('d', function(d) { return path(d.points); })
.attr('marker-end', 'url(#arrow)');
axis.append('text')
.attr('transform', function(d) {
var p = projection(d.labelLocation);
return 'translate(' + p[0] + ',' + p[1] + ')';
})
.attr('dy', '0.33em')
.style('text-anchor', 'middle')
.text(function(d) { return d.label; });
var cube = g.append('g').attr('class', 'cube');
var side = cube.selectAll('path').data(cubeData())
.enter().append('path')
.attr('class', function(d) { return 'side ' + d.className; })
.attr('d', function(d) { return path(d.points) + 'Z'; });
function update() {
side.attr('d', function(d) { return path(d.points) + 'Z'; });
axis.select('path')
.attr('d', function(d) { return path(d.points); });
axis.select('text')
.attr('transform', function(d) {
var p = projection(d.labelLocation);
return 'translate(' + p[0] + ',' + p[1] + ')';
});
}
d3.timer(function(elapsed) {
projection
.pitch((Math.PI / 6) * Math.sin(elapsed / 10000))
.yaw((elapsed / 2000));
update();
});
function cubeData() {
return [
{
className: 'bottom',
points: [
[-50, -50, -50],
[-50, +50, -50],
[+50, +50, -50],
[+50, -50, -50]
]
},
{
className: 'left',
points: [
[+50, -50, +50],
[+50, +50, +50],
[+50, +50, -50],
[+50, -50, -50]
]
},
{
className: 'right',
points: [
[-50, +50, +50],
[+50, +50, +50],
[+50, +50, -50],
[-50, +50, -50]
]
},
{
className: 'left',
points: [
[-50, -50, +50],
[-50, +50, +50],
[-50, +50, -50],
[-50, -50, -50]
]
},
{
className: 'right',
points: [
[-50, -50, +50],
[+50, -50, +50],
[+50, -50, -50],
[-50, -50, -50]
]
},
{
className: 'top',
points: [
[-50, -50, +50],
[-50, +50, +50],
[+50, +50, +50],
[+50, -50, +50]
]
},
];
}
function axesData() {
return [
{
label: 'x',
labelLocation: [225, 0, 0],
points: [
[0, 0, 0],
[200, 0, 0]
]
},
{
label: 'y',
labelLocation: [0, 225, 0],
points: [
[0, 0, 0],
[0, 200, 0]
]
},
{
label: 'z',
labelLocation: [0, 0, 225],
points: [
[0, 0, 0],
[0, 0, 200]
]
}
];
}
function isometricProjection() {
var sin = Math.sin,
cos = Math.cos,
asin = Math.asin,
tan = Math.atan,
PI = Math.PI;
var pitch = PI / 6,
yaw = PI / 4,
alpha = asin(tan(pitch)),
beta = yaw;
// See https://en.wikipedia.org/wiki/Isometric_projection
// TODO: Figure out why ax, ay and az needed to be flipped around.
function project(point) {
var ax = point[1],
ay = -point[2],
az = point[0];
var x = cos(beta) * ax - sin(beta) * az,
y = cos(alpha) * ay + sin(alpha) *
(sin(beta) * ax + cos(beta) * az);
return [x, y];
}
project.pitch = function(x) {
if (!arguments.length) return alpha;
pitch = x;
alpha = Math.asin(Math.tan(pitch));
return project;
};
project.yaw = function(x) {
if (!arguments.length) return beta;
yaw = x;
beta = yaw;
return project;
};
return project;
}
function isometricPath() {
var projection = function(d) { return d.slice(0, 2); };
function path(points) {
return 'M' +
points
.map(function(point) { return projection(point).join(','); })
.join('L');
}
path.projection = function(x) {
if (!arguments.length) return projection;
projection = x;
return path;
};
return path;
}
function radiansToDegrees(radians) { return radians * 180 / Math.PI; }
function degreesToRadians(degrees) { return degrees * Math.PI / 180; }
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment