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

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:

Now put the code for creating `<circles>` 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 `<circle>` 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");
yScale.range([HEIGHT,0]);//set the visual range (e.g. 600 to 0)
varyDomain=d3.extent(runs,function(datum,index){
returndatum.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 <circle> to the <svg>
d3.selectAll('circle')
.attr('cy',function(datum,index){
returnyScale(datum.distance);
});
varparseTime=d3.timeParse("%B%e, %Y at %-I:%M%p");
varformatTime=d3.timeFormat("%B%e, %Y at %-I:%M%p");
varxScale=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){
returnxScale(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
});
varyScale=d3.scaleLinear();//create the scale
yScale.range([HEIGHT,0]);//set the visual range (e.g. 600 to 0)
varyDomain=d3.extent(runs,function(datum,index){
returndatum.distance;//compare distance properties of each item in the data array
})
yScale.domain(yDomain);
varrender=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 <circle> to the <svg>
d3.selectAll('circle')
.attr('cy',function(datum,index){
returnyScale(datum.distance);
});
d3.selectAll('circle')
.attr('cx',function(datum,index){
returnxScale(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();
varbottomAxis=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
varcreateTable=function(){
d3.select('tbody').html('');//clear out all rows from the table
for(vari=0;i<runs.length;i++){
varrow=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(){
varx=d3.event.offsetX;//gets the x position of the mouse relative to the svg element
vary=d3.event.offsetY;//gets the y position of the mouse relative to the svg element
vardate=xScale.invert(x)//get a date value from the visual point that we clicked on
vardistance=yScale.invert(y);//get a numeric distance value from the visual point that we clicked on
varnewRun={//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