Skip to content

Instantly share code, notes, and snippets.

@austinhyde

austinhyde/LICENSE

Last active Jun 5, 2020
Embed
What would you like to do?
3D Bar Chart
function bar3d() {
var config = {
x: prop('x'),
y: prop('y'),
z: prop('z'),
width: prop('width'),
height: prop('height'),
depth: prop('depth'),
camera: [0, 0, 1]
};
function bar3d(g) {
g.each(function(d){
var g = d3.select(this);
var faces = getFaces.apply(this, arguments);
_.each(faces, function(points, name) {
var path = g.selectAll('.face.'+name).data([name]);
path.exit().remove();
path.enter().append('path')
.attr('class', 'face ' + name);
d3.transition(path)
.attr('d', svgHelp.polygon(points));
});
});
}
function getFaces() {
// resolve absolute positions
// x,y,z is left top front corner of the bar
var left = config.x.apply(this, arguments);
var right = left + config.width.apply(this, arguments);
var top = config.y.apply(this, arguments);
var bottom = top + config.height.apply(this, arguments);
var front = config.z.apply(this, arguments);
var back = front + config.depth.apply(this, arguments);
var cx = config.camera[0];
var cy = config.camera[1];
var cz = config.camera[2];
var projection = perspective(config.camera);
var faces = {};
if (front > cz) {
faces.front = projection([
[left, top, front],
[left, bottom, front],
[right, bottom, front],
[right, top, front]
]);
}
if (top > cy) {
faces.top = projection([
[left, top, front],
[left, top, back],
[right, top, back],
[right, top, front]
]);
}
if (left > cx) {
faces.left = projection([
[left, top, front],
[left, top, back],
[left, bottom, back],
[left, bottom, front]
]);
}
if (right < cx) {
faces.right = projection([
[right, top, front],
[right, top, back],
[right, bottom, back],
[right, bottom, front]
]);
}
return faces;
}
accessor(bar3d, config, 'camera', true);
accessor(bar3d, config, 'x');
accessor(bar3d, config, 'y');
accessor(bar3d, config, 'z');
accessor(bar3d, config, 'width');
accessor(bar3d, config, 'height');
accessor(bar3d, config, 'depth');
return bar3d;
}
letter frequency
A .08167
B .01492
C .02782
D .04253
E .12702
F .02288
G .02015
H .06094
I .06966
J .00153
K .00772
L .04025
M .02406
N .06749
O .07507
P .01929
Q .00095
R .05987
S .06327
T .09056
U .02758
V .00978
W .02360
X .00150
Y .01974
Z .00074
<!DOCTYPE html>
<html lang="en">
<head>
<title>3d Bar Chart</title>
<style>
.bar .face {
shape-rendering: geometricPrecision;
stroke: #4286b4;
stroke-width: .7px;
}
.bar .face.front {
fill: #4286b4;
}
.bar .face.top {
fill: #3b82bd;
}
.bar .face.left,
.bar .face.right {
fill: #3675a9;
}
.bar:hover .face {
stroke: #A52A2A;
}
.bar:hover .face.front {
fill: #A52A2A;
}
.bar:hover .face.top {
fill: #991920;
}
.bar:hover .face.left,
.bar:hover .face.right {
fill: #8e181e;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"></script>
<script type="text/javascript" src="utils.js"></script>
<script type="text/javascript" src="bar3d.js"></script>
<script type="text/javascript">
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40,
front: 0,
back: 0
};
var width = 960 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
var depth = 100 - margin.front - margin.back;
var xScale = d3.scale.ordinal()
.rangeRoundBands([0, width], .2);
var yScale = d3.scale.linear()
.range([height, 0]);
var zScale = d3.scale.ordinal()
.domain([0, 1, 2])
.rangeRoundBands([0, depth], .4);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.ticks(10, '%');
var chart = d3.select('#chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', svgHelp.translate(margin.left, margin.right));
d3.tsv('data.tsv', type, function(err, data) {
if (err) return;
xScale.domain(_.sortBy(_.uniq(_.map(data, 'letter'))));
yScale.domain([0, _.max(data, 'frequency').frequency]);
function x(d) { return xScale(d.letter); }
function y(d) { return yScale(d.frequency); }
var camera = [width / 2, height / 2, -1000];
var barGen = bar3d()
.camera(camera)
.x(x)
.y(y)
.z(zScale(0))
.width(xScale.rangeBand())
.height(function(d) { return height - y(d); })
.depth(xScale.rangeBand());
chart.append('g')
.attr('class', 'x axis')
.attr('transform', svgHelp.translate(0, height))
.call(xAxis);
chart.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('transform', svgHelp.rotate(-90))
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Frequency');
var extent = xScale.rangeExtent();
var middle = (extent[1] - extent[0]) / 2;
chart.selectAll('g.bar').data(data)
.enter().append('g')
.attr('class', 'bar')
// sort based on distance from center, so we draw outermost
// bars first. otherwise, bars drawn later might overlap bars drawn first
.sort(function(a, b) {
return Math.abs(x(b) - middle) - Math.abs(x(a) - middle);
})
.call(barGen);
});
function type(d) {
d.frequency = +d.frequency;
return d;
}
</script>
</body>
</html>
Copyright 2020 Austin Hyde
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// some helpers for working with svg
var svgHelp = {
translate: function(x, y) {
return 'translate(' + x.toFixed(2) + ',' + y.toFixed(2) + ')';
},
rotate: function(d) {
return 'rotate(' + d.toFixed(2) + ')';
},
polygon: function (points) {
points = points.map(function(p) {
return p.map(prop('toFixed', 2)).join(',');
});
return 'M' + points.join('L') + 'Z';
}
};
// generate a projection transformation from 3d to 2d
function perspective(camera) {
var cx = camera[0],
cy = camera[1],
cz = camera[2];
return function (points) {
return points.map(function(point) {
var px = point[0],
py = point[1],
pz = point[2];
return [ ((cx - px) * cz) / (pz - cz) + cx,
((cy - py) * cz) / (pz - cz) + cy ];
})
};
}
function functor(x) {
return _.isFunction(x) ? x : function() { return x };
}
// prop('x')({ x: 3 }) -> 3
// prop('slice', 1, 1)([0, 1, 2, 3]) -> [1]
function prop(p) {
var args = _.rest(arguments);
return function(o) {
return functor(o[p]).apply(o, args);
};
}
function accessor(target, config, name, isNotFunctor) {
target[name] = function (value) {
if (!arguments.length) return config[name];
config[name] = isNotFunctor ? value : functor(value);
return target;
};
}
@rubel714

This comment has been minimized.

Copy link

@rubel714 rubel714 commented Nov 19, 2017

Hello,
How can I create 3d group bar using your D3 concept??

@rajkamal90

This comment has been minimized.

Copy link

@rajkamal90 rajkamal90 commented May 8, 2018

is their any licence using it for my project

@tekawadetushar87

This comment has been minimized.

Copy link

@tekawadetushar87 tekawadetushar87 commented Jun 26, 2018

Could you please share the example for D3 3D group bar chart.

@minniekp

This comment has been minimized.

Copy link

@minniekp minniekp commented Jun 5, 2020

This code has a bug. It is not working for me. I am facing issues in bar3d function. Has anyone tried the above code? Please clarify

@austinhyde

This comment has been minimized.

Copy link
Owner Author

@austinhyde austinhyde commented Jun 5, 2020

Super late but added an MIT license.

@minniekp This code is working just fine, see http://bl.ocks.org/austinhyde/02a038074dbd9a7b1eb3

Note that this uses a very old version of d3.

@minniekp

This comment has been minimized.

Copy link

@minniekp minniekp commented Jun 5, 2020

@minniekp

This comment has been minimized.

Copy link

@minniekp minniekp commented Jun 5, 2020

var camera = [width / 2, height / 2, -1000];
var barGen = bar3d()
.camera(camera)
.x(x)
.y(y)
.z(zScale(0))
.width(xScale.rangeBand())
.height(function(d) { return height - y(d); })
.depth(xScale.rangeBand());

I am getting an error in the above code saying "property camera does not exist on type (g: any) => void" when I try to call on bar3d() function. Please let me know what I am doing wrong here.

Also I need to make a cube on the left side of the bars for all bars in a grouped bar chart and stacked bar chart. Can you please tell me how to achieve this in 3d?

@minniekp

This comment has been minimized.

Copy link

@minniekp minniekp commented Jun 5, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment