diff --git a/BUILD.md b/BUILD.md index 0b653df..05ed212 100644 --- a/BUILD.md +++ b/BUILD.md @@ -633,7 +633,7 @@ Now the browser should look like this: ## Create click handler -Let's say that we want it so that when the user clicks on the `` element, it creates a new run. +Let's say that we want it so that when the user clicks on the `` element, it creates a new run. Add the following to the bottom of `app.js`: ```javascript d3.select('svg').on('click', function(){ @@ -653,11 +653,13 @@ d3.select('svg').on('click', function(){ }); ``` -You might notice that `createTable()` just adds on all the run rows again +Let's examine what we just wrote. `d3.select('svg').on('click', function(){` Sets up a click handler on the `svg` element. The anonymous function that gets passed in as the second parameter to `.on()` gets called each time the user clicks on the SVG. Once inside that callback function, we use `d3.event.offsetX` to get the x position of the mouse inside the SVG and `d3.event.offsetY` to get the y position. We then use `xScale.invert()` and `yScale.invert()` to turn the x/y visual points into data values (date and distance, respectively). We then use those data values to create a new run object. We create an id for the new run by getting the id of the last element in the `runs` array and adding 1 to it. Lastly, we push the new run onto the `runs` array and call `createTable()`. + +Click on the SVG to create a new run. You might notice that `createTable()` just adds on all the run rows again ![](https://i.imgur.com/Vu2CwCI.png) -Let's clear out the rows previous created and re-render everything: +Let's alter the `createTable()` function so that when it runs, it clears out any rows previously created and re-renders everything. Add `d3.select('tbody').html('')` to the top of the `createTable` function in `app.js`: ```javascript var createTable = function(){ @@ -671,9 +673,11 @@ var createTable = function(){ } ``` +Now refresh the page, and click on the SVG to create a new run. The table should look like this now: + ![](https://i.imgur.com/YcoPxK7.png) -Now put the code for creating `` inside a render function: +The only issue now is that circles aren't being created when you click on the SVG. To fix this, let's wrap the code for creating `` elements in a render function, and call `render()` immediately after it's defined: ```javascript var render = function(){ @@ -713,7 +717,7 @@ var render = function(){ render(); ``` -For future use, let's move the `xScale` and `yScale` out of the render function along with the code for creating the domains/ranges: +If you refresh the browser, you'll see an error in the console. This is because the `bottomAxis` and `leftAxis` use `xScale` and `yScale` which are now scoped to exist only inside the `render()` function. For future use, let's move the `xScale` and `yScale` out of the render function along with the code for creating the domains/ranges: ```javascript var parseTime = d3.timeParse("%B%e, %Y at %-I:%M%p"); @@ -752,14 +756,21 @@ var render = function(){ render(); ``` -Let's call `render()` inside our `` click handler: +Now go to the bottom of `app.js` and add a line to call `render()` inside our `` click handler: ```javascript +var newRun = { //create a new "run" object + id: runs[runs.length-1].id+1, //generate a new id by adding 1 to the last run's id + date: formatTime(date), //format the date object created above to a string + distance: distance //add the distance +} runs.push(newRun); createTable(); render(); //add this line ``` +Now when you click the SVG, a circle will appear: + ![](https://i.imgur.com/5KjqmNp.png) ## Remove data diff --git a/examples/scatter_plot/app.js b/examples/scatter_plot/app.js index 1f1ad0e..1adb2ac 100644 --- a/examples/scatter_plot/app.js +++ b/examples/scatter_plot/app.js @@ -24,23 +24,6 @@ d3.select('svg') .style('width', WIDTH) .style('height', HEIGHT); -var yScale = d3.scaleLinear(); //create the scale -yScale.range([HEIGHT, 0]); //set the visual range (e.g. 600 to 0) -var yDomain = d3.extent(runs, function(datum, index){ - return datum.distance; //compare distance properties of each item in the data array -}) -yScale.domain(yDomain); - -d3.select('svg').selectAll('circle') //since no circles exist, we need to select('svg') so that d3 knows where to append the new circles - .data(runs) //attach the data as before - .enter() //find the data objects that have not yet been attached to visual elements - .append('circle'); //for each data object that hasn't been attached, append a to the - -d3.selectAll('circle') - .attr('cy', function(datum, index){ - return yScale(datum.distance); - }); - var parseTime = d3.timeParse("%B%e, %Y at %-I:%M%p"); var formatTime = d3.timeFormat("%B%e, %Y at %-I:%M%p"); var xScale = d3.scaleTime(); @@ -50,10 +33,30 @@ var xDomain = d3.extent(runs, function(datum, index){ }); xScale.domain(xDomain); -d3.selectAll('circle') - .attr('cx', function(datum, index){ - return xScale(parseTime(datum.date)); //use parseTime to convert the date string property on the datum object to a Date object, which xScale then converts to a visual value - }); +var yScale = d3.scaleLinear(); //create the scale +yScale.range([HEIGHT, 0]); //set the visual range (e.g. 600 to 0) +var yDomain = d3.extent(runs, function(datum, index){ + return datum.distance; //compare distance properties of each item in the data array +}) +yScale.domain(yDomain); +var render = function(){ + + d3.select('svg').selectAll('circle') //since no circles exist, we need to select('svg') so that d3 knows where to append the new circles + .data(runs) //attach the data as before + .enter() //find the data objects that have not yet been attached to visual elements + .append('circle'); //for each data object that hasn't been attached, append a to the + + d3.selectAll('circle') + .attr('cy', function(datum, index){ + return yScale(datum.distance); + }); + + d3.selectAll('circle') + .attr('cx', function(datum, index){ + return xScale(parseTime(datum.date)); //use parseTime to convert the date string property on the datum object to a Date object, which xScale then converts to a visual value + }); +} +render(); var bottomAxis = d3.axisBottom(xScale); //pass the appropriate scale in as a parameter d3.select('svg') @@ -67,6 +70,7 @@ d3.select('svg') .call(leftAxis); //no need to transform, since it's placed correctly initially var createTable = function(){ + d3.select('tbody').html(''); //clear out all rows from the table for (var i = 0; i < runs.length; i++) { var row = d3.select('tbody').append('tr'); row.append('td').html(runs[i].id); @@ -76,3 +80,20 @@ var createTable = function(){ } createTable(); + +d3.select('svg').on('click', function(){ + var x = d3.event.offsetX; //gets the x position of the mouse relative to the svg element + var y = d3.event.offsetY; //gets the y position of the mouse relative to the svg element + + var date = xScale.invert(x) //get a date value from the visual point that we clicked on + var distance = yScale.invert(y); //get a numeric distance value from the visual point that we clicked on + + var newRun = { //create a new "run" object + id: runs[runs.length-1].id+1, //generate a new id by adding 1 to the last run's id + date: formatTime(date), //format the date object created above to a string + distance: distance //add the distance + } + runs.push(newRun); //push the new run onto the runs array + createTable(); //render the table + render(); //add this line +});