/* built with Studio Sketchpad:
* https://sketchpad.cc
*
* observe the evolution of this sketch:
* https://studio.sketchpad.cc/sp/pad/view/ro.-mCrjQhN0gp/rev.7815
*
* authors:
* Bethany
* Danielle Wegrzyn
* Nate
*
*
* Pinhead Larry
* Matthew Levin
*
*
* Matthew Levin
* kalina
* license (unless otherwise specified):
* creative commons attribution-share alike 3.0 license.
* https://creativecommons.org/licenses/by-sa/3.0/
*/
/********************************************************
* Description to go here
********************************************************/
// general globals
color bg = color(255); // background color
PieChart burrito; // the pie chart
IngredientLabelList ingredients; // ingredients
SliceData infochan; //data to be displayed on hover for ingredients
// ingredient data variables
String INGREDIENT_DATA_PATH = "/static/uploaded_resources/p.18469/calories.csv";
final static String CHIPOTLE = "Chipotle";
final static String CALTOR = "California Tortilla";
final static String QDOBA = "Qdoba";
final static String MOES = "Moe\'s";
var CALORIES = {};
var PRICES = {};
// ingredient label constants
String RESTAURANT = CALTOR; // currently selected restaurant
// String LABEL_FONT_FILE = ""; // file for ingredient label font
int LABEL_SIZE = 18; // font size for ingredient labels
// animation constants
int ANIM_RATE = 0.2; // how fast to animate
int ANIM_THRESHOLD = 0.1; // how close to the target to stop animation
int HOVER_SLICE_RATE = 25;
int HOVER_SLICE_GROWTH = 60;
int SELECTED_COUNT = 0;
// test
RestaurantButton[] restaurantButtons;
void setup() {
size(800, 750);
font = loadFont("Courier New");
burrito = new PieChart(width/3.4, height/2 + height/7, width/2.2);
ingredients = new IngredientLabelList(width/8 - 45, height/6, burrito);
infochan = new SliceData (width/2 + 100, height/2 + 50, null);
restaurantButtons = new RestaurantButton[4];
restaurantButtons[0] = new RestaurantButton(500, 625, CALTOR, "/static/uploaded_resources/p.18465/Bugger_King_Logo.svg");
restaurantButtons[1] = new RestaurantButton(560, 625, CHIPOTLE, "/static/uploaded_resources/p.18465/Bugger_King_Logo.svg");
restaurantButtons[2] = new RestaurantButton(620, 625, MOES, "/static/uploaded_resources/p.18465/Bugger_King_Logo.svg");
restaurantButtons[3] = new RestaurantButton(680, 625, QDOBA, "/static/uploaded_resources/p.18465/Bugger_King_Logo.svg");
loadData();
SELECTED_COUNT = 0;
}
void draw() {
checkHover();
background(bg);
noStroke();
burrito.drawObject();
ingredients.drawObject();
infochan.drawObject();
for(int i = 0; i < restaurantButtons.length; i++) {
restaurantButtons[i].drawObject();
}
}
// checks the color at the current mouse position, expands any hovered slice
void checkHover() {
loadPixels();
int pixel = mouseX + mouseY * width;
color cAtMouse = color(red(pixels[pixel]), green(pixels[pixel]), blue(pixels[pixel]));
boolean found = false;
for(int i = 0; i < burrito.slices.length; i++) {
if (cAtMouse == burrito.slices[i].col){
burrito.slices[i].setHover(true);
found = true;
//elsewhere, we check if slice.hover and draw its info
}
else{
burrito.slices[i].setHover(false);}
}
//if we didn't find anything hovered over, set info to null
if (!found){
infochan.pieSlice = null;
}
}
// proof of concept: add a new slice of random size and color on left click, remove last added on right click
int i = 0;
void mousePressed() {
if(mouseButton == LEFT) {
for(IngredientLabel ingredient : ingredients.ingredients) {
if(ingredient.checkClick(mouseX, mouseY)) {
ingredient.toggleIngredient();
}
}
for(int i = 0; i < restaurantButtons.length; i++) {
if(restaurantButtons[i].checkClick(mouseX, mouseY)) {
restaurantButtons[i].onClick();
}
}
///burrito.addPieSlice("thing" + i++, random(10, 1000), color(random(255), random(255), random(255)));
}
}
/********************************************************
* interface class for drawable objects
* all objects that need to be drawn will subclass this class
* (e.g. cars and planes are subclasses of vehicle; they both drive,
* so the vehicle class might have a drive() method, but cars and planes
* implement them differently)
* this way, we can guarantee that all drawable objects can be drawn using
* drawnObject(), but they can be implemented individually
********************************************************/
class IDrawable {
int xPos, yPos;
void drawObject();
}
// individual slice of a pie chart
class PieSlice extends IDrawable {
String name;
int xPos, yPos, radius, value;
double currentSize, nextSize, currentRadius, nextRadius; // x, y, and radius of graph, slice value
color col; // slice color
boolean hover;
PieSlice(String name, int value, color col) {
this.name = name;
this.value = value;
this.col = col;
this.hover = false;
}
void setPosition(int xPos, int yPos, int radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
this.currentRadius = 0;
this.nextRadius = radius;
this.currentSize = 0;
}
void setHover(boolean hover) {
this.hover = hover;
if(hover) {
nextRadius = radius + HOVER_SLICE_GROWTH;
} else {
nextRadius = radius;
}
}
// update the size for animating
double updateSize(int total, double startRads) {
nextSize = percentToRads(value/total) + startRads;
return nextSize;
}
// immediately set the current size (will not animate)
double setSize(int total, double startRads) {
nextSize = percentToRads(value/total) + startRads;
currentSize = nextSize;
return nextSize;
}
// animate slice resizing
void animate() {
if(currentSize < nextSize - ANIM_THRESHOLD) {
currentSize += ANIM_RATE;
} else if(currentSize > nextSize + ANIM_THRESHOLD) {
currentSize -= ANIM_RATE;
} else {
currentSize = nextSize;
}
if(currentRadius < nextRadius - HOVER_SLICE_RATE) {
currentRadius += HOVER_SLICE_RATE;
} else if(currentRadius > nextRadius + HOVER_SLICE_RATE) {
currentRadius -= HOVER_SLICE_RATE;
} else {
currentRadius = nextRadius;
}
}
void drawObject(double startRads) {
fill(col);
arc(xPos, yPos, currentRadius, currentRadius, startRads, currentSize);
if(currentSize != nextSize || currentRadius != nextRadius) {
animate();
}
if(this.hover) {
//set hovered pie slice
infochan.pieSlice = this;
}else{
}
}
}
//class for displaying ingredient info
class SliceData extends IDrawable{
PieSlice pieSlice; //the slice hovered
void drawObject(){
fill(100);
textSize(18);
text("TOTAL", this.xPos, this.yPos - 60);
fill(0);
textSize(72);
text(burrito.total + 300, this.xPos, this.yPos);
fill(100);
textSize(42);
text("cal", this.xPos + (42 * ((burrito.total + 300).toString().length)), this.yPos);
if (pieSlice != null){
// drop shadow for ingredient
fill(100);
textSize(28);
text(this.pieSlice.name.toLowerCase(), this.xPos +.5, this.yPos +40 +.5);
text(this.pieSlice.value + " cal", this.xPos +.5, this.yPos +70 + .5);
// ingredient text
fill(this.pieSlice.col);
textSize(28);
text(this.pieSlice.name.toLowerCase(), this.xPos, this.yPos +40);
text(this.pieSlice.value + " cal", this.xPos, this.yPos +70);
}
}
SliceData(int xPos,int yPos, PieSlice pieSlice){
this.xPos = xPos;
this.yPos = yPos;
this.pieSlice = pieSlice;
}
}
// class for the entire pie chart, containing many pie slices
class PieChart extends IDrawable {
color borderColor; // color of surrounding border
int borderSize, radius; // border size and radius of the graph
int total; // total value of all slices in the graph
PieSlice[] slices; // array of slices
PieChart(int xPos, int yPos, int radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
total = 0;
borderColor = color(234, 230, 199);
borderSize = 40;
slices = new int[0]; // create empty slice list
}
// add a new slice to the graph
void addPieSlice(String name, int value, color col) {
PieSlice slice = new PieSlice(name, value, col);
slice.setPosition(xPos, yPos, radius);
slices.push(slice); // add slice to the list of slices
updateTotal(); // recount the total value of the graph
}
// get the index of an individual pie slice
int getPieSliceIndex(String name) {
int index = -1;
for(int i = 0; i < slices.length; i++) {
if(slices[i].name == name) {
index = i;
break;
}
}
return i;
}
// update the value of a pie slice
void updatePieSlice(String name, int newValue) {
int index = getPieSliceIndex(name);
if(index > -1) {
slices[index].value = newValue;
if(SELECTED_COUNT == 1 && newValue > 0) {
slices[index].currentRadius = 0;
}
updateTotal();
}
}
// remove a pie slice
void removePieSlice(String name) {
int index = getPieSliceIndex(name);
if(index > -1) {
slices.splice(index, 1);
updateTotal();
}
}
// recount the toal value of the graph
void updateTotal() {
total = 0;
for(int i = 0; i < slices.length; i++) {
total += slices[i].value;
}
updateSlices();
}
// set slices to animate to new sizes
void updateSlices() {
double lastRads = 0; // percent of the last slice drawn
for(int i = 0; i < slices.length-1; i++) {
lastRads = slices[i].updateSize(total, lastRads);
}
if(slices.length > 1) {
slices[slices.length-1].setSize(total, lastRads);
} else {
slices[0].updateSize(total, 0);
}
}
void print() {
for(int i = 0; i < slices.length; i++) {
console.log(slices[i].name + "\t" + slices[i].value);
}
console.log("======================================");
}
void drawObject() {
fill(borderColor);
ellipse(xPos, yPos, radius+borderSize, radius+borderSize); // border
double lastRads = 0; // percent of the last slice drawn
for(int i = 0; i < slices.length; i++) {
slices[i].drawObject(lastRads); // draw this slice
lastRads = slices[i].currentSize;
}
}
}
// given a percentage as a decimal, converts it to radians for display in pie graph
int percentToRads(double percent) {
return radians(percent * 360);
}
// individual ingredient label
class IngredientLabel extends IDrawable {
String name;
String category;
boolean selected;
PieChart graph;
Color col;
var calories = {};
int originX, originY, width, height;
IngredientLabel(String name, String category, int xPos, int yPos, PieChart graph, color col) {
this.name = name;
this.category = category;
this.graph = graph;
this.selected = true;
this.col = col;
this.loadCalories();
this.graph.addPieSlice(this.name, this.calories[RESTAURANT], this.col);
this.toggleIngredient();
setPosition(xPos, yPos);
}
void setPosition(int x, int y) {
this.xPos = x;
this.yPos = y;
this.originX = this.xPos-5;
this.originY = this.yPos + LABEL_SIZE/2.5;
this.width = 110;
this.height = LABEL_SIZE * 1.5;
}
// TOUDOU: load calories from databaseMAKI-CHAN!!!
void loadCalories() {
this.calories = CALORIES[this.name];
}
// toggle ingredient's selected state
void toggleIngredient() {
if(!this.selected) {
SELECTED_COUNT++;
this.selected = true;
this.graph.updatePieSlice(this.name, this.calories[RESTAURANT]);
} else {
SELECTED_COUNT--;
this.selected = false;
this.graph.updatePieSlice(this.name, 0);
}
}
// remove ingredient from graph, read with new restaurant
void updateRestaurant() {
boolean sel = selected;
if(sel) {
this.toggleIngredient();
if(this.calories[RESTAURANT] > 0) {
this.toggleIngredient();
}
}
}
boolean checkClick(int x, int y) {
if(this.calories[RESTAURANT] > 0) {
int originX = this.xPos-5;
int originY = this.yPos + LABEL_SIZE/3.5;
if(x >= this.originX-12 && x <= (this.originX + this.width + 12)
&& y <= this.originY && y >= (this.originY - this.height)) {
return true;
}
}
return false;
}
void drawObject() {
noStroke();
fill(255);
rect(this.originX, this.originY, this.width, -this.height);
if(this.selected) {
if(this.calories[RESTAURANT] > 0) {
stroke(0);
} else {
stroke(180);
}
stroke(0);
strokeWeight(2);
fill(color(this.col));
rect(this.originX - 12, this.originY - 19, 12, 12);
if(this.calories[RESTAURANT] > 0) {
fill(0);
} else {
fill(180);
}
} else {
if(this.calories[RESTAURANT] > 0) {
stroke(0);
} else {
stroke(180);
}
strokeWeight(2);
rect(this.originX - 12, this.originY - 19, 12, 12);
if(this.calories[RESTAURANT] > 0) {
fill(0);
} else {
fill(180);
}
}
text(name.toLowerCase(), xPos, yPos);
noStroke();
}
}
// list of all ingredient labels
class IngredientLabelList extends IDrawable {
IngredientLabel[] ingredients;
var ingredientCategories = {};
PieChart graph;
int ingredientSpacing;
int colWidth = 150;
IngredientLabelList(int xPos, int yPos, PieChart graph) {
ingredients = new IngredientLabel[0];
this.graph = graph;
this.xPos = xPos;
this.yPos = yPos;
}
// add ingredient to list
void addIngredient(String name, String category, color col) {
IngredientLabel label = new IngredientLabel(name, category, xPos, yPos + (ingredients.length * LABEL_SIZE * 1.5), graph, col);
this.ingredients.push(label);
// this.graph.print();
if(this.ingredientCategories[category] == null) {
this.ingredientCategories[category] = new ArrayList();
}
this.ingredientCategories[category].add(label);
this.updateColumns();
}
void updateRestaurants() {
for(int i = 0; i < ingredients.length; i++) {
ingredients[i].updateRestaurant();
}
}
void updateColumns() {
String[] keys = Object.keys(this.ingredientCategories);
for(int i = 0; i < keys.length; i++) {
for(int j = 0; j < this.ingredientCategories[keys[i]].size(); j++) {
IngredientLabel label = this.ingredientCategories[keys[i]].get(j);
label.setPosition(this.xPos + (i * this.colWidth), this.yPos + (1 + j*LABEL_SIZE*1.5));
}
}
}
void toggleIngredient(String name) {
for(int i = 0; i < this.ingredients.length; i++) {
if(this.ingredients[i].name == name) {
this.ingredients[i].toggleIngredient();
break;
}
}
}
void drawObject() {
textSize(LABEL_SIZE+4);
fill(100);
String[] keys = Object.keys(this.ingredientCategories);
for(int i = 0; i < keys.length; i++) {
text(keys[i].toLowerCase(), this.xPos+(i * this.colWidth)-19, this.yPos/1.35);
}
textSize(LABEL_SIZE);
fill(0);
for(int i = 0; i < ingredients.length; i++) {
ingredients[i].drawObject();
}
}
}
/***********************************
* Restaurant buttons
***********************************/
class RestaurantButton {
String name;
PShape image;
int originX, originY, width, height;
int size;
RestaurantButton(int xPos, int yPos, String name, String imagePath) {
this.xPos = xPos;
this.yPos = yPos;
this.name = name;
this.size = 30;
this.image = loadShape(imagePath);
}
boolean checkClick(int x, int y) {
return (x >= this.xPos && x <= (this.xPos + this.size)
&& y >= this.yPos && y <= (this.yPos + this.size));
}
void onClick() {
RESTAURANT = this.name;
ingredients.updateRestaurants();
}
void drawObject() {
if(RESTAURANT == this.name) {
fill(210, 200, 0);
}
else {
fill(bg);
}
// textSize(LABEL_SIZE);
// text(this.name, this.xPos, this.yPos);
ellipse(this.xPos + this.size/2, this.yPos + this.size/2, this.size + 10, this.size + 10);
shape(image, this.xPos, this.yPos, this.size, this.size);
}
}
/************************************
* Data
***********************************/
void makeIngredientCalories(String name, int caltor, int chipotle, int moes, int qdoba) {
var ingredient = {};
ingredient[CALTOR] = caltor;
ingredient[CHIPOTLE] = chipotle;
ingredient[MOES] = moes;
ingredient[QDOBA] = qdoba;
CALORIES[name] = ingredient;
}
void makeIngredientPrices(String name, int caltor, int chipotle, int moes, int qdoba) {
var ingredient = {};
ingredient[CALTOR] = caltor;
ingredient[CHIPOTLE] = chipotle;
ingredient[MOES] = moes;
ingredient[QDOBA] = qdoba;
PRICES[name] = ingredient;
}
void loadData() {
Strings[] data = loadStrings(INGREDIENT_DATA_PATH);
for(String line : data) {
String arr = line.split(',');
makeIngredientCalories(arr[0], int(arr[5]), int(arr[4]), int(arr[2]), int(arr[3]));
ingredients.addIngredient(arr[0], arr[1], color(int(arr[6]), int(arr[7]), int(arr[8])));
}
}