> show canvas only <


/* 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])));
  }
}