Skip to content

Instantly share code, notes, and snippets.

@kielni
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kielni/9334854 to your computer and use it in GitHub Desktop.
Save kielni/9334854 to your computer and use it in GitHub Desktop.
d3 order processing
<html>
<head>
<style>
rect {
fill: #D2B48C;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-size: 0.8em;
}
.description {
margin-top: 1em;
}
</style>
</head>
<body>
<div class="description">
Visualization of orders according to these rules:
<ul>
<li>There should be no visually overlapping processing times.
<li>An order whose processing time overlaps with other order(s) should have the same width as those order(s).
<li>An order should occupy the maximum possible width on the canvas while still observing the second rule.
</ul>
</div>
<div id="orders2d"></div>
<div class="description">
The visualization above wastes space. The width of the bars is visually important, but conveys little information and
obscures other information. For example, the changing widths make it difficult to visually compare order durations;
which took longer, #2 or #7? Order #5 took the longest, but order #2 occupies the most area. It's complicated to calculate the widths, and they aren't even that useful. Maybe this is interesting as a puzzle, but as a visualization, it could be better.
</div>
<div class="description">
Rotating the time axis and keeping all widths constant makes it easy to see both how orders overlapped in time, and to
compare their durations. This version shows the same or more information in less space. It's also simpler to generate the
data needed to draw the visualization.
</div>
<div id="orders1d"></div>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script>
<script type="text/javascript">
var orders = [
{"orderId" : 1, "packingStart" : 224, "duration" : 69 },
{"orderId" : 2, "packingStart" : 335, "duration" : 91 },
{"orderId" : 3, "packingStart" : 23, "duration" : 47 },
{"orderId" : 4, "packingStart" : 130, "duration" : 52 },
{"orderId" : 5, "packingStart" : 5, "duration" : 183 },
{"orderId" : 6, "packingStart" : 253, "duration" : 71 },
{"orderId" : 7, "packingStart" : 41, "duration" : 68 }
];
orders = _.sortBy(orders, "packingStart");
var maxWidth = 400;
var maxHeight = 300;
var streams = new Array();
_.each(orders, function(order) {
var ridx = -1;
order.packingEnd = order.packingStart+order.duration;
// find first row where order.packingStart >= end time of the row
var r = _.find(streams, function(row) {
ridx++;
// if end time of last order in this row is less than start, add it
return _.last(row).packingEnd <= order.packingStart;
});
if (_.isUndefined(r)) {
// not found, make a new row
order.stream = streams.length;
streams.push(new Array(order));
} else {
// add to existing row
order.stream = ridx;
r.push(order);
}
// calculate widths for 2d version
var width = maxWidth / (order.stream+1)
order.width = width;
var minStart = order.packingStart;
var maxEnd = order.packingEnd;
// look for overlapping orders in the streams to the left
for (var i = 0; i < order.stream; i++) {
_.find(streams[i], function(o) {
if (o.packingStart > maxEnd)
return true; // stop
// overlap
if ((o.packingStart <= minStart && o.packingEnd >= minStart) ||
(o.packingStart >= minStart && o.packingStart <= maxEnd)) {
// use the smallest width
var w = Math.min(o.width, width);
o.width = order.width = w;
minStart = Math.min(minStart, o.packingStart);
maxEnd = Math.max(maxEnd, o.packingEnd);
}
return false; // keep looking
});
}
});
// draw
var margin = { top: 5, right: 5, bottom: 5, left: 35 };
var width = maxWidth - margin.left - margin.right;
var height = maxHeight - margin.top - margin.bottom;
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([0, height]);
// 2d
x.domain([0, width]);
y.domain([0, 600]);
var svg2d = d3.select("#orders2d").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg2d.selectAll(".box")
.data(orders)
.enter()
.append("rect")
.attr("x", function(d) { return x(d.width*d.stream)+1 })
.attr("y", function(d) { return y(d.packingStart) })
.attr("width", function(d) { return x(d.width)-1; })
.attr("height", function(d) { return y(d.duration) } );
svg2d.selectAll(".box")
.data(orders)
.enter()
.append("text")
.attr("x", function(d) { return x(d.width*d.stream)+10 })
.attr("y", function(d) { return y(d.packingStart)+y(d.duration)*3/4 })
.text(function(d) { return "#"+d.orderId });
var yaxis = d3.svg.axis()
.scale(y)
.orient("left");
svg2d.append("g")
.attr("class", "axis")
.call(yaxis);
// 1d
maxHeight = 100;
margin = { top: 5, right: 10, bottom: 35, left: 5 };
height = maxHeight - margin.top - margin.bottom;
x.domain([0, 600]);
y = d3.scale.linear().range([0, height]);
y.domain([0, streams.length]);
var barHeight = streams.length > 1 ? y(1)-y(0) : height;
var svg1d = d3.select("#orders1d").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg1d.selectAll(".box")
.data(orders)
.enter()
.append("rect")
.attr("x", function(d) { return x(d.packingStart) })
.attr("y", function(d) { return y(d.stream) })
.attr("width", function(d) { return x(d.duration) })
.attr("height", barHeight-2);
svg1d.selectAll(".box")
.data(orders)
.enter()
.append("text")
.attr("x", function(d) { return x(d.packingStart)+5 })
.attr("y", function(d) { return y(d.stream)+barHeight*3/4 })
.text(function(d) { return "#"+d.orderId });
var xaxis = d3.svg.axis()
.scale(x)
.orient("bottom");
svg1d.append("g")
.attr("class", "axis")
.attr("transform", "translate(0,"+ (height)+")")
.call(xaxis);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment