
// **************************************
//            Custom mappings
// **************************************

// Map from button names to actual categories
// Some smaller categories are grouped on the UI
var mapCategories = {
  'Arson': ['ARSON'],
  'Assault': ['ASSAULT'],
  'Burglary': ['BURGLARY'],
  'Narcotic': ['DRUG/NARCOTIC'],
  'DUI': ['DRIVING UNDER THE INFLUENCE'],
  'Kidnapping': ['KIDNAPPING'],
  'Sex Offense': ['SEX OFFENSES, FORCIBLE', 'SEX OFFENSES, NON FORCIBLE'],
  'Missing Person': ['MISSING PERSON', 'RUNAWAY'],
  'Robbery': ['ROBBERY'],
  'Suspicious Occ.': ['SUSPICIOUS OCC'],
  'Theft': ['LARCENY/THEFT', 'STOLEN PROPERTY'],
  'Vandalism': ['VANDALISM'],
  'Vehicle Theft': ['VEHICLE THEFT'],
  'Warrants': ['WARRANTS'],
  'Weapon Laws': ['WEAPON LAWS'],
  'White Collar': ['EMBEZZLEMENT', 'FRAUD', 'FORGERY/COUNTERFEITING',
                    'BRIBERY', 'EXTORTION'],
  'Other': ['OTHER OFFENSES', 'PROSTITUTION', 'LIQUOR LAWS', 'FAMILY OFFENSES',
              'GAMBLING', 'DRUNKENNESS', 'DISORDERLY CONDUCT', 'SECONDARY CODES',
              'TRESPASS', 'LOITERING'],
  'Non Criminal': ['NON-CRIMINAL', 'SUICIDE', 'RECOVERED VEHICLE']
}

// This is needed for calculating histograms
var mapCategoriesReverse = {
  'ARSON': 'Arson',
  'ASSAULT': 'Assault',
  'BURGLARY': 'Burglary',
  'DRUG/NARCOTIC': 'Narcotic',
  'DRUNKENNESS': 'Other',
  'DISORDERLY CONDUCT': 'Other',
  'DRIVING UNDER THE INFLUENCE': 'DUI',
  'KIDNAPPING': 'Kidnapping',
  'SEX OFFENSES, FORCIBLE': 'Sex Offense',
  'MISSING PERSON': 'Missing Person',
  'RUNAWAY': 'Missing Person',
  'ROBBERY': 'Robbery',
  'SECONDARY CODES': 'Other',
  'SUSPICIOUS OCC': 'Suspicious Occ.',
  'LARCENY/THEFT': 'Theft',
  'STOLEN PROPERTY': 'Theft',
  'TRESPASS': 'Other',
  'LOITERING': 'Other',
  'VANDALISM': 'Vandalism',
  'VEHICLE THEFT': 'Vehicle Theft',
  'WARRANTS': 'Warrants',
  'WEAPON LAWS': 'Weapon Laws',
  'EMBEZZLEMENT': 'White Collar',
  'FRAUD': 'White Collar',
  'FORGERY/COUNTERFEITING': 'White Collar',
  'BRIBERY': 'White Collar',
  'EXTORTION': 'White Collar',
  'OTHER OFFENSES': 'Other',
  'PROSTITUTION': 'Other',
  'LIQUOR LAWS': 'Other',
  'FAMILY OFFENSES': 'Other',
  'SEX OFFENSES, NON FORCIBLE': 'Sex Offense',
  'GAMBLING': 'Other',
  'NON-CRIMINAL': 'Non Criminal',
  'SUICIDE': 'Non Criminal',
  'RECOVERED VEHICLE': 'Non Criminal'
}

// Map from button names to actual categories
// Group categories on the UI to simplify understanding
var mapResolutions = {
  'None': ['NONE'],
  'Arrest': ['ARREST, BOOKED', 'JUVENILE BOOKED', 'ARREST, CITED'],
  'Other': [
    'NOT PROSECUTED', 'PSYCHOPATHIC CASE', 'UNFOUNDED',
    'CLEARED-CONTACT JUVENILE FOR MORE INFO', 'EXCEPTIONAL CLEARANCE',
    'LOCATED', 'COMPLAINANT REFUSES TO PROSECUTE']
}

// Needed for calculating histograms
var mapResolutionsReverse = {
  'NONE': 'None',
  'ARREST, BOOKED': 'Arrest',
  'JUVENILE BOOKED': 'Arrest',
  'ARREST, CITED': 'Arrest',
  'NOT PROSECUTED': 'Other',
  'PSYCHOPATHIC CASE': 'Other',
  'UNFOUNDED': 'Other',
  'CLEARED-CONTACT JUVENILE FOR MORE INFO': 'Other',
  'EXCEPTIONAL CLEARANCE': 'Other',
  'LOCATED': 'Other',
  'COMPLAINANT REFUSES TO PROSECUTE': 'Other'
}


// **************************************
//          Global Vars
// **************************************

var firstTime = true;  // not everything gets drawn initially

// These contain data for the histograms
var time_histogram = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
var category_histogram = {
  'Arson':0,
  'Assault':0,
  'Burglary':0,
  'Narcotic':0,
  'DUI':0,
  'Kidnapping':0,
  'Sex Offense':0,
  'Missing Person':0,
  'Robbery':0,
  'Suspicious Occ.':0,
  'Theft':0,
  'Vandalism':0,
  'Vehicle Theft':0,
  'Warrants':0,
  'Weapon Laws':0,
  'White Collar':0,
  'Other':0,
  'Non Criminal':0
}
var resolution_histogram = {'None':0, 'Arrest':0, 'Other':0}
var formattedCategoryData = [];    // to fit requirements for d3 chart
var formattedTimeData = [];        // to fit requirements for d3 chart
var histogramTotalCount = 0;       // contains total # of data points at any time
var numCircles = 0;  // limit to 2

// Needed for UI (?)
// var tChartSVG = null;
// var rChartSVG = null;
// var cChartSVG = null;


// **************************************
//          Read in the data
// **************************************

// Done through the <script> tag in html file
// JSON file is in assets/scpd_incidents.json
// The variable "dataset" now stores JSON string
var cleanDataset = dataset["data"]


// **************************************
//     Set up the SVG Map
//     Code from CS448b website
// **************************************

var width = 750; var height = 750;

// Set up projection that map is using
// This is the mapping between <longitude, latitude> position
// to <x, y> pixel position on the map
// projection([lon, lat]) returns [x, y]
// Use this to put points on the SVG map
var projection = d3.geo.mercator()
  .center([-122.433701, 37.767683]) // San Francisco, roughly
  .scale(225000)
  .translate([width / 2, height / 2]);

// Add an svg element to the DOM
var svg = d3.select("#map-div").append("svg")
  .attr("width", width)
  .attr("height", height);

// Add svg map at correct size
svg.append("image")
  .attr("width", width)
  .attr("height", height)
  .attr("xlink:href", "assets/sf-map.svg");


// **************************************
//     Add points to map initially
// **************************************

// Make the points ellipses to avoid selecting
// the location filters when selecting all circles
var initialPointOpacity = 0.2;
var visiblePointOpacity = 0.7;
var initialPointColor = 'white';
var visiblePointColor = 'red';
svg.selectAll("ellipse")
  .data(cleanDataset)
  .enter()
  .append("ellipse")
  .attr("cx", function(d) {
    return projection(d["Location"])[0];
  })
  .attr("cy", function(d) {
    return projection(d["Location"])[1];
  })
  .attr("rx", 2)
  .attr("ry", 2)
  .attr('fill-opacity', initialPointOpacity)
  .attr('fill', initialPointColor)
  // SHOW TOOLTIP ON HOVER
  // .on('mouseover', showReportInfo)
  // .on('mouseout', hideReportInfo)
;

// Populate the histograms as well
redrawHistograms();
drawTimeLabels();
firstTime = false;


// **************************************
//    Redraw Points
// **************************************

// When a filter changes - update these vars
var include_categories = []
var include_resolutions = []
var location1 = null;
var location2 = null;
var time_min = 0;
var time_max = 24;

// When a filter changes - call this to redraw
function redrawPoints() {

    svg.selectAll('ellipse')
      .attr('fill-opacity', function(d, i) {

        // check location filter
        if (numCircles == 1) {
          if (distanceBetweenCircles(d3.select(this), location1) > location1.attr('r')) {
          return 0;  }}
        if (numCircles == 2) {
          var distanceToCircle1 = distanceBetweenCircles(d3.select(this), location1);
          var distanceToCircle2 = distanceBetweenCircles(d3.select(this), location2);
          if (twoDistinctCircles() == false) {  // check for intersection
            if ((distanceToCircle1 > location1.attr('r')) || (distanceToCircle2 > location2.attr('r'))) {
              return 0; }}
          else {  // just check if it's in either circle
            if ((distanceToCircle1 > location1.attr('r')) && (distanceToCircle2 > location2.attr('r'))) {
              return 0; }}
        }
        // OLD LOGIC - if two circles don't intersect, then show nothing
        // if (location1 != null) {
        //   if (distanceBetweenCircles(d3.select(this), location1) > location1.attr('r')) {
        //     return 0;  }}
        // if (location2 != null) {
        //   // we passed location1 check but need to check location2
        //   // this logic is equivalent to checking for intersections
        //   if (distanceBetweenCircles(d3.select(this), location2) > location2.attr('r')) {
        //     return 0;  }}

        // check time filter
        var time = Number(d['Time'].slice(0,2));
        if (time < time_min) {return 0;}
        if (time > time_max) {return 0;}

        // check category filter
        var category = d['Category']
        if (include_categories.length > 0) {
          if (include_categories.indexOf(category) < 0) {return 0;}}

        // check resolution filter
        var resolution = d['Resolution']
        if (include_resolutions.length > 0) {
          if (include_resolutions.indexOf(resolution) < 0) {return 0;}}

        // if any filter fails above - return 0 to make point transparent
        // if all filters are passed - then color in the point below
        // idea is to break as soon as a filter fails to avoid extra computation
        return visiblePointOpacity;

      })
      .attr('fill', function(d) {
        // special case - reset to initial appearance when no filters at all
        if (include_categories.length == 0 && include_resolutions.length == 0
            && location1 == null && location2 == null && time_min == 0 && time_max == 24)
            {return initialPointColor;}
        // otherwise if any filter is selected
        else {return visiblePointColor;}
      })
      .attr('rx',2.5)
      .attr('ry',2.5)
    ;
}

// Show detailed report info on mouseover of point
// function showReportInfo(d) {

//   if (Number(d3.select(this).attr('fill-opacity')) > 0) {
  
//     console.log(d3.select(this));

//     svg.append('text')
//       .attr('x', 100)
//       .attr('y', 100)
//       .text('Report Info' + d['Category'])
//       .attr('fill', '#ffffff')
//       .attr('font-family', 'Courier')
//       .attr('font-size', 15)
//       .attr('id', 'reportInfo')
//     ;
//   }
//   // .html(function(d) { return '<strong>' + d['label'] + '</strong><br>' + d['value']; })
// }
// function hideReportInfo(d) {
//   if (Number(d3.select(this).attr('fill-opacity')) > 0) {
//     svg.select('#reportInfo').remove();
//   }
// }

// Helper function
function distanceToCircle(x1, y1, d3circle) {
  var x2 = d3circle.attr('cx');
  var y2 = d3circle.attr('cy');
  return Math.sqrt(Math.pow(x1-x2,2) + Math.pow(y1-y2,2));
}

// Helper function
function distanceBetweenCircles(d3circle1, d3circle2) {
  var x1 = d3circle1.attr('cx');
  var y1 = d3circle1.attr('cy');
  var x2 = d3circle2.attr('cx');
  var y2 = d3circle2.attr('cy');
  return Math.sqrt(Math.pow(x1-x2,2) + Math.pow(y1-y2,2));
}


// **************************************
//     Filter by category
// **************************************

// When a category button is clicked
$('.category-btn').on('click', function() {

  // logic in this function is a bit messy
  // since buttonValue != category to be removed
  // since one buttonValue maps to multiple 'true' categories
  var buttonValue = $(this).attr('value');
  var associatedCategories = mapCategories[buttonValue];

  // figure out if button was already selected or not
  var temp = [];
  var alreadySelected = false;
  for (var i = 0; i < include_categories.length; i++) {
    if (associatedCategories.indexOf(include_categories[i]) > -1) {alreadySelected = true;}
    else {temp.push(include_categories[i]);} // add to new temp list we are building
  }

  // toggle depending on whether it was already selected
  if (alreadySelected == true) {
    include_categories = temp;
    $(this).removeClass('btn-selected-categories');
  }
  if (alreadySelected == false) {
    temp = include_categories.concat(associatedCategories);
    include_categories = temp;
    $(this).addClass('btn-selected-categories');
  }

  // Update UI either way
  redrawPoints();
  redrawHistograms();

});

// When all categories are cleared
$('.category-clear').on('click', function() {
  $('.btn-selected-categories').removeClass('btn-selected-categories'); // update the button UI
  include_categories = []
  redrawPoints();
  redrawHistograms();
});

// REMOVED - this functionality because I think it's too confusing - I want people to focus on other interactions
// When hovering over button - highlight the corresponding bubble in chart
// $('.category-btn').mouseenter(function() {
//   var buttonValue = $(this).attr('value');
//   var bubbleID = '#' + buttonValue.split(' ')[0];
//   d3.select(bubbleID).attr('fill', 'red');
// });
// $('.category-btn').mouseleave(function() {
//   var buttonValue = $(this).attr('value');
//   var bubbleID = '#' + buttonValue.split(' ')[0];
//   d3.select(bubbleID).attr('fill', '#bababa');
// })


// **************************************
//     Filter by Incident Resolution
// **************************************

// When a resolution is selected
// Same logic as for categories
$('.resolution-btn').on('click', function() {

  var buttonValue = $(this).attr('value');
  var associatedResolutions = mapResolutions[buttonValue];

  // figure out if button was already selected or not
  var temp = [];
  var alreadySelected = false;
  for (var i = 0; i < include_resolutions.length; i++) {
    if (associatedResolutions.indexOf(include_resolutions[i]) > -1) {alreadySelected = true;}
    else {temp.push(include_resolutions[i]);} // add to new temp list we are building
  }

  // toggle depending on whether it was already selected
  if (alreadySelected == true) {
    include_resolutions = temp;
    $(this).removeClass('btn-selected-resolutions');
  }
  if (alreadySelected == false) {
    temp = include_resolutions.concat(associatedResolutions);
    include_resolutions = temp;
    $(this).addClass('btn-selected-resolutions');
  }

  // Update UI either way
  redrawPoints();
  redrawHistograms();

});

// When all categories are cleared
$('.resolution-clear').on('click', function() {
  $('.btn-selected-resolutions').removeClass('btn-selected-resolutions'); // update the button UI
  include_resolutions = []
  redrawPoints();
  redrawHistograms();
});


// ***********************************************
//            Filter by Location
// ***********************************************

// Drag the circles to reposition
var drag = d3.behavior.drag()
  .on('drag', dragMove)
  .on('dragend', redrawHistograms) // only after dragend, not while
;

// Drag the edges of circle to resize
var dragr = d3.behavior.drag()
  .on('drag', dragResize)
  .on('dragend', redrawHistograms) // only after dragend, not while
;

// Implementing the drag behaviors
function dragMove() {

  var innerCircle = d3.select(this);
  var outerCircle = d3.select('#outerCircle' + innerCircle.attr('id').slice(-1));
  var radiusLine = d3.select('#radiusLine' + innerCircle.attr('id').slice(-1));
  
  // calculate offsets & locations before moving anything
  var offsetX = outerCircle.attr('cx') - innerCircle.attr('cx')
  var offsetY = outerCircle.attr('cy') - innerCircle.attr('cy')
  var newX = d3.event.x;
  var newY = d3.event.y;
  var newOuterX = newX + offsetX;
  var newOuterY = newY + offsetY;

  // move everything together
  innerCircle.attr('cx', newX).attr('cy', newY);
  outerCircle.attr('cx', newOuterX).attr('cy', newOuterY);
  radiusLine.attr('x1', newX).attr('x2', newOuterX).attr('y1', newY).attr('y2', newOuterY);

  // computationally slow - but we do this for smooth interaction
  redrawPoints(); // don't redraw the histograms yet
}

function dragResize() {

  var outerCircle = d3.select(this);
  var innerCircle = d3.select('#innerCircle' + outerCircle.attr('id').slice(-1));
  var radiusLine = d3.select('#radiusLine' + outerCircle.attr('id').slice(-1));
  var newX = d3.event.x;
  var newY = d3.event.y;
  var newRadius = distanceToCircle(newX, newY, innerCircle)

  // move the outer circle to its new position
  outerCircle.attr('cx', newX).attr('cy', newY);

  // resize the inner circle based on updated radius
  innerCircle.attr('r', newRadius);

  // redraw the connector line. only the outer node changes.
  radiusLine.attr('x2', newX).attr('y2', newY);

  // calculate the new radius in miles
  var radiusInMiles = calcRadiusInMiles(
    [radiusLine.attr('x1'), radiusLine.attr('y1')],
    [radiusLine.attr('x2'), radiusLine.attr('y2')]);
  var elementID = outerCircle.attr('id').slice(-1)
  $('#location' + elementID + '-desc').text('location ' + elementID + ' radius = ' + radiusInMiles);

  // computationally slow - but we do this for smooth interaction
  redrawPoints(); // don't redraw the histograms yet
}

// Helper function
function calcRadiusInMiles(coordA, coordB) {
  // take in 2 arrays representing coordinates in SVG space
  latlonA = projection.invert(coordA);
  latlonB = projection.invert(coordB);
  var miles = d3.geo.distance(latlonA, latlonB) * 3963.2;
  // return in easy to print format
  return miles.toString().slice(0,4) + ' miles';
}

// Helper function
function twoDistinctCircles() {
  if (numCircles < 2) { return false; }
  var circle1 = d3.select('#innerCircle1');
  var circle2 = d3.select('#innerCircle2');
  var distanceBetweenCenters = distanceBetweenCircles(circle1, circle2);
  var sumOfRadii = Number(circle1.attr('r')) + Number(circle2.attr('r'));
  if (distanceBetweenCenters > sumOfRadii) { return true; }
  return false;
}

// CREATE A LOCATION FILTER ON DOUBLE CLICK
svg.on('dblclick', function(d) {

  if (numCircles >= 2) {
    alert('You cannot set more than 2 location filters');
    return;
  }
  numCircles = numCircles + 1;  // circles will be 1-indexed

  // Use this ID to group inner and outer circles together
  var outerID = 'outerCircle' + numCircles;
  var innerID = 'innerCircle' + numCircles;
  var lineID = 'radiusLine' + numCircles;

  // Record location of click = circle center
  var coordinates = d3.mouse(this);
  var initialRadius = 80;

  // Larger circle that can be dragged to reposition
  // Append this first so it will be at the back
  svg.append('circle')
    .attr('cx', coordinates[0])
    .attr('cy', coordinates[1])
    .attr('r', initialRadius)
    .attr('fill', 'yellow')
    .attr('fill-opacity', 0.2)
    .attr('stroke', 'yellow')
    .attr('stroke-width', 2)
    .attr('cursor', 'move')
    .attr('id', innerID)
    .call(drag)
  ;

  // Line that shows the radius
  var radiusLine = svg.append('line')
    .attr('stroke', 'yellow')
    .attr('x1', coordinates[0])
    .attr('x2', coordinates[0] + initialRadius)
    .attr('y1', coordinates[1])
    .attr('y2', coordinates[1])
    .attr('id', lineID)
  ;

  // Tiny circle that will be the resize mechanism
  // Lives on the outer edge of the main circle
  svg.append('circle')
    .attr('cx', coordinates[0] + initialRadius)
    .attr('cy', coordinates[1])
    .attr('r', 5)
    .attr('fill', 'yellow')
    .attr('cursor', 'pointer')
    .attr('id', outerID)
    .call(dragr)
  ;

  // Update the globals and then the UI
  if (numCircles == 1) { location1 = d3.select('#innerCircle1'); }
  if (numCircles == 2) { location2 = d3.select('#innerCircle2'); }
  redrawPoints();
  redrawHistograms();

  // Update text in the location filter box
  var radiusInMiles = calcRadiusInMiles(
    [radiusLine.attr('x1'), radiusLine.attr('y1')],
    [radiusLine.attr('x2'), radiusLine.attr('y2')]);
  if (numCircles == 1) {$('#location1-desc').text('location 1 radius = ' + radiusInMiles);}
  if (numCircles == 2) {$('#location2-desc').text('location 2 radius = ' + radiusInMiles);}

  // Hint for user in special case
  // Decided to change the UX so this won't be an issue
  // if (twoDistinctCircles() == true) {
  //   alert('There are no reports in the intersection');
  // }
  
});

// Clear the location filters
$('.location-clear').on('click', function() {
  // remove elements from UI
  svg.selectAll('circle').remove();
  svg.selectAll('line').remove();
  // update global vars
  numCircles = 0;      
  location1 = null;
  location2 = null;
  // update the rest of UI
  redrawPoints();    
  redrawHistograms();
  // clear the radius descriptions
  $('#location1-desc').text('');
  $('#location2-desc').text('');
});


// **************************************
//     Filter by Time Of Day
// **************************************

// Set up the slider UI
$('#time-slider').slider({
  range: true,
  min: 0,
  max: 23,
  values: [0, 23],
  slide: function( event, ui ) {
    time_min = ui.values[0];
    time_max = ui.values[1];
  },
  stop: function( event, ui ) {
    redrawPoints();
    redrawHistograms();
  }
});

// Call this when time filter is cleared
$('.time-clear').on('click', function() {
  // Remove filters from global vars
  time_min = 0;
  time_max = 23;
  // Reset the slider handles
  $('#time-slider').slider('values',[0,24]);
  // Update the graphics
  redrawPoints();
  redrawHistograms();
});


// ***************************************
//     Redraw histograms
// ***************************************

// Wrapper function that gets called every time
// a filter is added (right after points are redrawn)
function redrawHistograms() {
  calcHistograms();
  reformatHistogramData();
  drawTimeChart();
  drawCategoryChart();
  drawResolutionChart();
  if (firstTime == false) {
    drawTotalCount();
  }
}

function drawTotalCount() {
  svg.selectAll('text').remove()
  svg.append('text')
    .attr('x', 0)
    .attr('y', 745)
    .text(histogramTotalCount + ' reports')
    .attr('fill', 'red')
    .attr('font-family', 'Courier')
    .attr('font-size', 15)
  ;
}


// ***************************************
//     Calculate the histogram data
// ***************************************

// Calculate the histograms based on existing filters
//    To make this easy - don't check all the filter logic again
//    Instead - redraw the points first
//    Then - depending on which points are showing
//    Calculate the histogram for those points
function calcHistograms() {

  // reset all of these because we're counting from scratch
  histogramTotalCount = 0;
  var timeTemp = [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  var categoryTemp = {
    'Arson':0,'Assault':0,'Burglary':0,'Narcotic':0,
    'DUI':0,'Kidnapping':0,'Sex Offense':0,
    'Missing Person':0,'Robbery':0,'Suspicious Occ.':0,
    'Theft':0,'Vandalism':0, 'Vehicle Theft':0,'Warrants':0,'Weapon Laws':0,
    'White Collar':0,'Other':0,'Non Criminal':0}
  var resolutionTemp = {'None':0, 'Arrest':0, 'Other':0}

  // loop through all data points and count histogram for each
  svg.selectAll('ellipse').each(function(d, i) {

    // find out if this point is showing. only count in histogram if it is.
    elementOpacity = d3.select(this).attr('fill-opacity');
    if (elementOpacity > 0) {

      var time = Number(d['Time'].slice(0,2));
      var category = mapCategoriesReverse[d['Category']]
      var resolution = mapResolutionsReverse[d['Resolution']]

      // update histogram accordingly
      timeTemp[time] += 1
      categoryTemp[category] += 1
      resolutionTemp[resolution] += 1
      histogramTotalCount += 1
    }

  });

  // finished counting histogram. reset the global vars.
  time_histogram = timeTemp;
  category_histogram = categoryTemp;
  resolution_histogram = resolutionTemp;

}

// This is a hack because d3 requires data in certain format for charts
// End result of this is to populate the global vars
function reformatHistogramData() {
  formattedCategoryData = []; formattedTimeData = []; // reset globals to empty for appending
  for (var key in category_histogram) {
    var dataPoint = {'label': key, 'value': category_histogram[key]};
    formattedCategoryData.push(dataPoint);
  }
  for (var i = 0; i < time_histogram.length; i++) { // time data must be appended in order
    var dataPoint = {'label': i, 'value': time_histogram[i]};
    formattedTimeData.push(dataPoint);
  }
}


// **************************************
//      Chart - Time Histogram
// **************************************

function drawTimeChart() {

  // dimensions of the div
  var width = 405; // hack - this should be dynamic
  var height = 85;

  // scaledHeight is a function that takes value, scales bar height accordingly, returns height
  var scaledHeight = d3.scale.linear().range([height, 0]);
  scaledHeight.domain([0, d3.max(formattedTimeData, function(d) {return d.value;})]);

  // distribute bars evenly
  var barWidth = width / formattedTimeData.length;

  // HACK: we recreate the chart below from scratch each time
  // TODO: a cleaner way to do this
  d3.select('#time-chart').selectAll('svg').remove();

  // create the SVG for the chart inside the div
  var tChartSVG = d3.select('#time-chart').append('svg')
                  .attr('width', width)
                  .attr('height', height)

  // bind data to the chart
  var tChart = tChartSVG.selectAll('g')
                .data(formattedTimeData)
                .enter()
                .append('g')
                .attr('transform', function(d,i) {
                  return "translate(" + i * barWidth + ",0)"; // puts bars at correct x-coord
                });

  // now that data is bound - append the rectangles and the text
  tChart.append('rect')
    .attr('y', function(d) {return scaledHeight(d.value);}) // we are working on reversed Y-axis
    .attr('height', function(d) {return height - scaledHeight(d.value);})
    .attr('width', barWidth-1)
    .attr('fill', '#3b3b3b')
    ;
}

// call this once to set up
function drawTimeLabels() {

  var width = 410; // hack - this should be dynamic
  var height = 15;
  var timeLabelSVG = d3.select('#time-label-div').append('svg')
                        .attr('width', width)
                        .attr('height', height);

  // add each label manually - hacky
  var fill = '#bababa'; var fontSize = 9;
  timeLabelSVG.append('text') .attr('x', 40).attr('y', height)
    .text('3 AM').attr('fill', fill).attr('font-size', fontSize);
  timeLabelSVG.append('text') .attr('x', 90).attr('y', height)
    .text('6 AM').attr('fill', fill).attr('font-size', fontSize);
  timeLabelSVG.append('text') .attr('x', 140).attr('y', height)
    .text('9 AM').attr('fill', fill).attr('font-size', fontSize);
  timeLabelSVG.append('text') .attr('x', 190).attr('y', height)
    .text('noon').attr('fill', fill).attr('font-size', fontSize);
  timeLabelSVG.append('text') .attr('x', 241).attr('y', height)
    .text('3 PM').attr('fill', fill).attr('font-size', fontSize);
  timeLabelSVG.append('text') .attr('x', 292).attr('y', height)
    .text('6 PM').attr('fill', fill).attr('font-size', fontSize);
  timeLabelSVG.append('text') .attr('x', 343).attr('y', height)
    .text('9 PM').attr('fill', fill).attr('font-size', fontSize);

  // add tick marks manually - hacky but need customization
  timeLabelSVG.append('polygon').style('fill', fill).attr('points', '50,0 55,4 45,4');
  timeLabelSVG.append('polygon').style('fill', fill).attr('points', '100,0 105,4 95,4');
  timeLabelSVG.append('polygon').style('fill', fill).attr('points', '150,0 155,4 145,4');
  timeLabelSVG.append('polygon').style('fill', fill).attr('points', '201,0 206,4 196,4');
  timeLabelSVG.append('polygon').style('fill', fill).attr('points', '251,0 256,4 246,4');
  timeLabelSVG.append('polygon').style('fill', fill).attr('points', '302,0 307,4 297,4');
  timeLabelSVG.append('polygon').style('fill', fill).attr('points', '353,0 358,4 348,4');
}


// ******************************************
//      Chart - Incident Resolution
// ******************************************

function drawResolutionChart() {

  var maxWidth = d3.select('#resolution-none').attr('width')-20;

  // function to scale the bar width accordingly
  var rChartScale = d3.scale.linear()
            .range([0,250])
            .domain([0, histogramTotalCount]);

  // HACK: we recreate the chart below from scratch each time
  // TODO: a cleaner way to do this
  d3.select('#resolution-none').selectAll('svg').remove();
  d3.select('#resolution-arrest').selectAll('svg').remove();
  d3.select('#resolution-other').selectAll('svg').remove();

  // create a space for the chart
  var svgA = d3.select('#resolution-none').append('svg').attr('width', 250).attr('height', 30);
  var svgB = d3.select('#resolution-arrest').append('svg').attr('width', 250).attr('height', 30);
  var svgC = d3.select('#resolution-other').append('svg').attr('width', 250).attr('height', 30);

  // create each block of the bar chart manually
  var widthA = rChartScale(resolution_histogram['None']);
  var widthB = rChartScale(resolution_histogram['Arrest']);
  var widthC = rChartScale(resolution_histogram['Other']);
  svgA.append('rect')
    .attr('x', 0)
    .attr('y', 5)
    .attr('height', 20)
    .attr('width', widthA)
    .attr('fill', '#5c5c5c')
  ;
  svgB.append('rect')
    .attr('x', 0)
    .attr('y', 5)
    .attr('height', 20)
    .attr('width', widthB)
    .attr('fill', '#5c5c5c')
  ;
  svgC.append('rect')
    .attr('x', 0)
    .attr('y', 5)
    .attr('height', 20)
    .attr('width', widthC)
    .attr('fill', '#5c5c5c')
  ;
}


// **************************************
//        Chart - Categories
// **************************************

function drawCategoryChart() {

  // break early for special case
  if (twoDistinctCircles() == true) { return; }

  // Modified example for basic bubble chart from
  // http://jrue.github.io/coding/2014/exercises/basicbubblepackchart/

  var maxDiameter = 220; // hack - this should be dynamic

  // specify the bubble chart layout
  var bubble = d3.layout.pack()
    .sort(null)
    .size([maxDiameter,maxDiameter])
    .padding(3)
  ;

  // create a tooltip object
  // modified code example from http://bl.ocks.org/Caged/6476579
  var tip = d3.tip()
    .attr('class', 'bubble-tip')
    .offset([-10, 0])
    .html(function(d) { return '<strong>' + d['label'] + '</strong><br>' + d['value']; })
  ;

  // HACK: we recreate the chart below from scratch each time
  // TODO: a cleaner way to do this
  d3.select('#category-chart').selectAll('svg').remove();

  // create the svg for the chart
  var categorySVG = d3.select('#category-chart').append('svg')
    .attr('width', maxDiameter)
    .attr('height', maxDiameter)
    .attr('class', 'bubble')
  ;

  // convert data to specific format needed for the bubble chart
  // another reason to recreate chart from scratch
  // we need to re-bubblify data every time
  var nodes = bubble.nodes({children:formattedCategoryData})
                .filter(function(d) {return !d.children;} );

  // bind data to the chart
  var categoryChart = categorySVG.append('g')
    .attr('transform', 'translate(0,0)')
    .selectAll('.bubble')
    .data(nodes)
    .enter()
  ;
  var forTooltip = categorySVG.select('g');
  forTooltip.call(tip);

  // draw the bubbles
  categoryChart.append('circle')
    .attr('r', function(d) {return d.r;} )
    .attr('cx', function(d) {return d.x;} )
    .attr('cy', function(d) {return d.y;} )
    .attr('fill', '#bababa')
    .attr('class', 'bubble')
    .attr('id', function(d) {
      return d['label'].split(' ')[0];
    })
    .on('mouseover', tip.show)
    .on('mouseout', tip.hide)
  ;

  // draw text labels when big enough
  categoryChart.append('text')
    .attr('x', function(d) {return d.x;} )
    .attr('y', function(d) {return d.y + 5;} )
    .attr('text-anchor', 'middle')
    .text(function(d) {
      if (d.r > 30) {return d['label'];}
      else {return '';}
    })
    .style({'fill': '#5c5c5c', 'font-size':9})
  ;

}


// **************************************
//              END
// **************************************











