Please excuse the awful coding practices that may be found here.
This was an experiment hashed out over a few days during the week before I started an internship at the Washington Post. You know, before I learned to stop making monolithic spaghetti-scripts. I wanted to build something quickly.
Drink recipes drawn in the spirit of Randall Munroe's "Up Goer Five" comic. This appears to be the "blueprint" color used in his new book, Thing Explainer.
I need more recipes, preferably those with an interesting history.
I'm running into issues with rendering this chart for printing. Additionally, some of my sizing does not appear to work the way I expect it to when using Firefox.
xxxxxxxxxx
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="print.css" media="print">
<link href="https://fonts.googleapis.com/css?family=Oleo+Script" rel="stylesheet" type="text/css">
<body>
<div id="info"></div>
</body>
<script src="d3.min.js"></script>
<script src="textures.min.js"></script>
<script src="definitions.js"></script>
<script>
/*
Margins
*/
var margin = {top: 20, right: 20, bottom: 20, left: 20},
padding = {top: 60, right: 60, bottom: 60, left: 60},
outerWidth = 960,
outerHeight = 768,
innerWidth = outerWidth - margin.left - margin.right,
innerHeight = outerHeight - margin.top - margin.bottom,
width = innerWidth - padding.left - padding.right,
height = innerHeight - padding.top - padding.bottom;
var underlay = d3.select("body").append("svg")
.attr("width", outerWidth)
.attr("height", outerHeight)
.attr("class", "paper");
var svg = underlay.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var diagram = svg.append("g")
.attr("class", "diagram")
.attr("transform", "translate(" + padding.left + "," + padding.top/2 + ")");
var menuGroup = svg.append("g")
.attr("class", "menuGroup")
.attr("transform", "translate(" + padding.left + "," + 475 + ")");
var menu_rect = menuGroup.append("rect")
.attr("class", "box")
.attr("width", innerWidth - padding.left - padding.right)
.attr("height", innerHeight - padding.bottom - 475);
var titleGroup = svg.append("g")
.attr("class", "title")
.attr("transform", "translate(" + 275 + "," + 400 + ")");
var specialGroup = svg.append("g")
.attr("class", "special")
.style("z-index", 101)
.attr("transform", "translate(" + padding.left/2 + "," + 300 + ")");
/*
Use definitions.js, where textures are defined
*/
var table = defineTextures(underlay)
var dimension = {"martini":
{"width": 350, "height": 200},
"oldFashioned":
{"width": 200, "height": 250}};
var cocktails,
cols = 5,
spacing = ((innerWidth - padding.left)/cols);
/*
Import Data
*/
d3.json("recipes.json", function(error, data) {
cocktails = data.cocktails.sort(function(a, b) {return a.name.localeCompare(b.name)});
var index = Math.floor(Math.random()*cocktails.length);
var choice = cocktails[index];
update(choice);
// array of cocktails to choose from with mouse interaction
var menu = menuGroup.selectAll("text")
.data(cocktails)
.enter()
.append("text")
.attr("class", "menu-item")
.attr("dx", function(d, i) {return padding.left + (i % cols) * spacing})
.attr("dy", function(d, i) {return padding.top/2 + Math.floor(i / cols) * spacing/3})
.attr("text-anchor", "middle")
.text(function(d) { return d.name})
.on("click", click)
.on("mouseover", mouseover)
.on("mouseout", mouseout);
})
/*
Draw
*/
function drawMartini(g) {
// a triangle to mask out the parts of the ingredients "outside" the glass
// draw stack in position for Martini glass
var stack = g.append("g")
.attr("id", "stack")
.attr("transform", "translate(" + 0 + "," + (195) + ")");
g.append("path")
.attr("d", " M 0,0 " + "L" + dimension.martini.width/2 + "," + dimension.martini.height +
"L" + 0 +"," + dimension.martini.height + "Z")
.attr("id", "martini-mask-left")
.attr("class", "hidden")
.style("z-index", 1);
// right hand side
g.append("path")
.attr("d", " M " + dimension.martini.width + "," + 0 + "L" + dimension.martini.width + "," + dimension.martini.height +
"L" + dimension.martini.width/2 +"," + dimension.martini.height + "Z")
.attr("id", "martini-mask-right")
.attr("class", "hidden")
.style("z-index", 1);
//left
g.append("line")
.attr("class", "glass")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", dimension.martini.width/2)
.attr("y2", dimension.martini.height);
//right
g.append("line")
.attr("class", "glass")
.attr("x1", dimension.martini.width)
.attr("y1", 0)
.attr("x2", dimension.martini.width/2)
.attr("y2", dimension.martini.height);
//stem
g.append("line")
.attr("class", "glass")
.attr("x1", dimension.martini.width/2)
.attr("y1", dimension.martini.height)
.attr("x2", dimension.martini.width/2)
.attr("y2", dimension.martini.height*2);
//base
g.append("line")
.attr("class", "glass")
.attr("x1", dimension.martini.width*0.25)
.attr("y1", dimension.martini.height*2)
.attr("x2", dimension.martini.width*0.75)
.attr("y2", dimension.martini.height*2);
return stack;
}
// returns stack group generated by draw
function drawSpecial(data, g) {
g.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("class", "garnish")
.attr("dx", 0)
.attr("dy", function(d, i) {return i * 30})
.style("z-index", 200)
.text(function(d) {return "* " + d});
}
// returns stack group generated by draw
function drawOldFashioned(g) {
var stack = g.append("g")
.attr("id", "stack")
.attr("transform", "translate(" + 100 + "," + (dimension.oldFashioned.height) + ")");
g.append("g").attr("transform", "translate(" + 100 + "," + (0) + ")").append("path")
.attr("d", "M 0,0 " + 0 + "," + dimension.oldFashioned.height + " " + dimension.oldFashioned.width
+ "," + dimension.oldFashioned.height + " " + dimension.oldFashioned.width + "," + 0)
.attr("class", "glass");
return stack;
}
var draw = {"martini": drawMartini, "oldFashioned": drawOldFashioned}
/*
Main Update
*/
function update(choice) {
// **** delete all hidden "masks", text, and previous renders****
diagram.selectAll(".hidden").remove();
diagram.selectAll(".glass").remove();
specialGroup.selectAll("text").remove();
svg.selectAll(".large-text").remove();
svg.selectAll(".info").remove();
svg.select("#stack").remove();
var name = titleGroup.append("text")
.attr("dx", 0)
.attr("dy", 0)
.attr("class", "large-text")
.text(choice.name)
// info paragraph
d3.select("#info")
.html("<p>" + choice.info + "</p>")
// ****
var ingredients = choice.ingredients,
garnishes = choice.special;
//stack is constructed by each particular glass' draw() method
var stack = draw[choice.glass].apply(null, [diagram]);
drawSpecial(garnishes, specialGroup);
var h1 = 0.0;
// normalize volume of entire mix
var numParts = 0;
var length = ingredients.length;
for(var i = 0; i < length; i++) {
numParts += ingredients[i].parts;
}
for(var i = 0; i < ingredients.length; i++){
var d = ingredients[i];
// area per unit of liquid is normalized, so each recipe has equal total volume
var h2 = calculateDepth(h1, d.parts, ((1/2*dimension.martini.width*dimension.martini.height)/2 - 50*dimension.martini.height/2)/numParts);
//group for a "level"
var g = stack.append("g")
.attr("transform", "translate(" + 0 + "," + -(h2)+ ")");
// textured ingredient
g.append("rect")
.attr("class", "level")
.attr("width", dimension[choice.glass].width - 2)
.attr("height", h2 - h1)
.attr("x", 1)
.style("stroke-color", "none")
.style("fill", table[d.key].texture.url());
// ingredient dividing line/separator
g.append("line")
.attr("x1", 1)
.attr("y1", 0)
.attr("x2", dimension[choice.glass].width -2)
.attr("y2", 1);
// ingredient name and quantity
g.append("text")
.attr("dx", 500)
.attr("dy", (h2-h1)/2 + 9)
.text(d.parts + " part" + ((d.parts == 1) ? "" : "s") + " " + table[d.key].name);
//stack ingredients
h1 += h2 - h1;
}
}
// account for changing depth and sloped sides of martini class in pours
function calculateDepth(h1, parts, unit){
var area = unit*parts,
theta = Math.atan((dimension.martini.width/2)/dimension.martini.height);
return Math.sqrt(2*area/Math.tan(theta) + h1*h1);
}
/*
Event Handlers
*/
function mouseover() {
d3.select(this).style("text-decoration", "underline")
}
function mouseout() {
d3.select(this).style("text-decoration", "none")
}
function click(d, i) {
update(d);
}
</script>