<!DOCTYPE html>
<html>
  <!-- HTML COMMENT -->
  
  <head>
    
    <!-- Dependencies and metadata usually go in the head section -->
    <title>D3 Practice</title>
    
    <!-- Imported JavaScript and CSS files -->
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    
    <!-- custom css -->
    <link rel='stylesheet' type='text/css' href='style.css'>
    
  </head>

  <body>
    <nav class="navbar navbar-inverse"> 
      <div class=container-fluid> 
        <div class=navbar-header> 
          <a href=# class=navbar-brand>D3 Tutorial, Simple Scatterplot</a> 
        </div> 
      </div> 
    </nav>
    
    <div class="container-fluid">
      <div class="row">
        <div id="container">
          <!-- svg will go here -->
        </div>
      </div>
      
      <hr/>
      
      <div class="col-4">
        <button class="btn btn-primary" onclick="regenerateData()">RandomizeData</button>
      </div>
      <br/>
      <div class="col-4">
        <button class="btn btn-info" onclick="addPoint()">Add a Random Point</button>
      </div>
      <br/>
      <div class="col-4">
        <button class="btn btn-danger" onclick="removePoint()">Remove a Random Point</button>
      </div>
    </div>
    
    <!-- custom JavaScript & CSS -->
    <script src="script.js"></script>
  </body>

</html>
// /*
// ####################################
// ########## CREATE DATASET ##########
// ####################################

// define a javascript object to hold our dataset
var dataset = {
  // points will be x,y coordinates
  points: [],
  numPoints: 25,
  color: "#913f92",
  radius: 6,
  minX: 0,
  maxX: 500,
  minY: 0,
  maxY: 1000
}

// define a function to randomly generate data for our scatterplot
var generateRandomPoints = function(dataset) {
  // datapoints should take the format { x: value, y: value }
  dataset.points = [];
  // shorthand way to randomly generate an array of values
  // Array.from({length: $n}, () => Math.random() * $upperBound + $lowerBound);
  // math.random generates a random num between 0 and 1

  var xValues = Array.from({
      length: dataset.numPoints
    }, () => Math.random() *
    dataset.maxX + dataset.minX);

  var yValues = Array.from({
      length: dataset.numPoints
    }, () => Math.random() *
    dataset.maxY + dataset.minY);

  for (var i = 0; i < xValues.length; i++) {
    dataset.points.push({
      x: xValues[i],
      y: yValues[i]
    })
  }
}

// run the function to actually generate our x,y coordianates 
generateRandomPoints(dataset);

// /*
// ########################################################################
// ##### This is where D3 gets extremely dense with function chaining #####
// ########################################################################


// ####################################
// ######### DEFINE D3 SCALES #########
// ####################################

// Define SVG size and padding (extra white space to prevent overlap).
var width = 600;
var height = 250;
var padding = { // buffer used to prevent chart element overlap
  top: 20,
  right: 25,
  bottom: 30,
  left: 60
}

// D3 scales are used to translate your numerical data into svg space

// define scales
var xScale = d3.scale.linear()                    // predefined d3 function to create scale
  .domain([dataset.minX, dataset.maxX])           // domain defines the coordinate system of your data
  .range([padding.left, width - padding.right]);  // range defines the coordinate system of your svg

var yScale = d3.scale.linear()
  .domain([dataset.minY, dataset.maxY])
  .range([height - padding.bottom, padding.top]); // notice that the larger value is the first listed 
                                                  // in the yScale range. 

// define axis behavior in D3
var xAxis = d3.svg.axis()                         // the d3 axis object
  .scale(xScale)                                  // set the scale to what we just defined
  .orient("bottom")                               // orient tick direction
  .ticks(5);                                      // try to do 5 ticks. Won't always be this many

var yAxis = d3.svg.axis()
  .scale(yScale)
  .orient('left')
  .ticks(5);
// /*

// ####################################
// ######## BUILD SVG USING D3 ########
// ####################################

// Use D3 to insert an SVG element into our HTML document
var svg = d3.select('#container')     // grab the <div> tag which has the id #svgcontainer
  .append('svg')                      // append an <svg> tag into this container
  .attr('width', width)               // set the svg's width attribute
  .attr('height', height);            // set the svg's height attribute

// /*
// actually insert x axis into html
svg.append('g')                               // new group element
  .attr('class', 'xAxis')                     // give it the css class 'xAxis'
  .attr('transform', 'translate' +
    '(0,' + (height - padding.bottom) + ')')  // translate the axis to the bottom of the svg
  .call(xAxis);                               // refer to axis variables to define scale behavior


svg.append('g')
  .attr('class', 'yAxis')
  .attr('transform', 'translate(' + padding.left + ',' + 0 + ')')
  .call(yAxis);

// /*
// ####################################
// ######### Helper Functions #########
// ####################################

// event handlers for interactivity
var handleMouseOver = function(d, i) {
  // Use D3 to select element, change color and size
    d3.select(this).attr({
      fill: "orange",
      r: dataset.radius * 2
    }).attr('stroke', "black").attr('stroke-width', "1px");

    // Specify where to put label of text
    svg.append("text").attr({
       id: "t" + Math.round(d.x) + "-" + Math.round(d.y) + "-" + i,  // Create an id for text so we can select it later for removing on mouseout
        x: function() { return xScale(d.x) - 30; },
        y: function() { return yScale(d.y) - 15; },
    })
    
    .text(function() {
      return [Math.round(d.x), Math.round(d.y)];  // Value of the text
    });
}

var handleMouseOut = function(d, i) {
  // Use D3 to select element, change color back to normal
  d3.select(this).attr({
    fill: dataset.color,
    r: dataset.radius
  }).attr('stroke', "none");

  // Select text by id and then remove
  d3.select("#t" + Math.round(d.x) + "-" + Math.round(d.y) + "-" + i).remove();  // Remove text location
}

// define a function to do the plotting to avoid code redundancy
var updateChart = function() {
  // Update scale domains. 
  // redefining the x and y minimums and maximums 
  xScale.domain([
    d3.min(dataset.points, function(d) {  // return minimum x value
      return d.x;
    }),                                   
    d3.max(dataset.points, function(d) { // return maximum x value
      return d.x;
    })
  ]);
  yScale.domain([
    d3.min(dataset.points, function(d) {  // return minimum y value
      return d.y;
    }),
    d3.max(dataset.points, function(d) {  // return maximum y value
      return d.y;
    })
  ]);

  // here's the d3 magic
  // this basically primes the svg to do stuff with <circle> tags

  // d3 does inventory and figures out how many <circle> tags currently exist in svg.
  var d3Circles = svg.selectAll('circle')
    .data(dataset.points);  // rebind dataset.points just in case it's been updated

  // if there aren't enough circle tags, .enter().append() creates new ones for the data points
  d3Circles.enter().append('circle')
    .attr('cx', function(d) {
      return xScale(0);           // initialize circle at origin 
    }).attr('cy', function(d) {
      return yScale(0);           // initialize circle at origin
    })
    .on("mouseover", handleMouseOver) // bind mouse-over event to each circle
    .on("mouseout", handleMouseOut);  // bind mouse-off event to each circle
    
  // exit().remove() will trash extra circles for us if we have too many
  d3Circles.exit()                // get svg elements we need to remove
    .transition()                 // default transition
    .attr('cx', function(d) {
      return xScale(0);           // initialize circle at origin 
    }).attr('cy', function(d) {
      return yScale(0);           // initialize circle at origin
    }).remove()                   // get rid of em
    

  // update circle locations, both new and old with fancy transitions 
  d3Circles
    .attr('fill', dataset.color)    // make dots purple by default
    
    // bind quick transition to visually indicate dots are going to change
    .transition()                   // start transition
    .duration(250)                  // 250 ms
    .ease('linear')                 // one type of 'transition acceleration'. linear
    .attr('r', dataset.radius / 2)  // make radius smaller
    .attr('fill', "gray")           // make dots gray by default
    
    
    // move the points using a different transition
    .transition()                   // start transition
    .duration(1500)                 // 1.5 seconds
    .ease('bounce')                 // bounce 'transition acceleration'.
    .attr('cx', function(d) {
      return xScale(d.x);           // move circle to new x coordinates 
    }).attr('cy', function(d) {
      return yScale(d.y);           // move circle to new y coordinates
    })
    
    // finish up by transitioning circles back to original color and size
    .transition()
    .duration(250)
    .ease('elastic')
    .attr('r', dataset.radius)
    .attr('fill', dataset.color)
    
  // update axes to represent new values
  svg.select('.xAxis')
    .transition()
    .duration(1000)
    .call(xAxis);

  svg.select('.yAxis')
    .transition()
    .duration(1000)
    .call(yAxis);
}

// run updateChart to plot initial set of points
updateChart();                    

// function for adding an additional point to our dataset
var addPoint = function() {
  dataset.numPoints++;

  var point = {
    x: Math.random() * dataset.maxX + dataset.minX,
    y: Math.random() * dataset.maxY + dataset.minY
  };

  dataset.points.push(point);
  updateChart();
}

// function for removing an additional point to our dataset
var removePoint = function() {
  if (dataset.numPoints === 0) {
    alert("Nah you can't do that!");
  } else {
    dataset.numPoints--;                      // lower numPoints by 1
    dataset.points.pop()                      // remove last entry in array
    updateChart();
  }
}

// function for regenerating random data for our dataset
var regenerateData = function() {
  generateRandomPoints(dataset);
  updateChart();
}
/* Styles go here */

/* Class Font Options */
.tick text {
  font-family: Montserrat, sans-serif;
  font-weight: 400;
}

label {
  font-family: Open Sans, sans-serif;
  font-size: 16px;
}

body {
  font-family: Open Sans, sans-serif;
  overflow-x: hidden; 
}

/* D3 CSS */
.chart .domain {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.xAxis path,
.yAxis path {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
  stroke-width: 3px;
}

.tick line { 
  stroke: black;
  stroke-width: 2px;
}