From a0ac6c696edb010f103d4cb2471165986683bc15 Mon Sep 17 00:00:00 2001 From: Matt Huntington Date: Sat, 1 Sep 2018 16:35:35 -0700 Subject: [PATCH] commenting scatter plot code --- examples/scatter_plot/app.css | 12 ++- examples/scatter_plot/app.js | 141 +++++++++++++++++++++---------- examples/scatter_plot/index.html | 5 ++ 3 files changed, 110 insertions(+), 48 deletions(-) diff --git a/examples/scatter_plot/app.css b/examples/scatter_plot/app.css index 09e7f03..62ebee0 100644 --- a/examples/scatter_plot/app.css +++ b/examples/scatter_plot/app.css @@ -1,26 +1,32 @@ circle { r:5; fill: black; + /* transitions to make growing and changing color look nice */ transition: r 0.5s linear, fill 0.5s linear; } #container { - overflow: visible; - margin-bottom: 50px; + overflow: visible; /* make sure axes are visible */ + margin-bottom: 50px; /* space below graph so table doesn't overlap */ } body { + /* move graph away from edge of screen */ margin: 20px 40px; } table, th, td { - border: 1px solid black; + /* make table cell borders visible */ + border: 1px solid black; } th, td { + /* make table cels look better */ padding:10px; text-align: center; } +/* hover state for circles */ circle:hover { r:10; fill: blue; } +/* styles for circles when you click on them */ circle:active { fill: red; } diff --git a/examples/scatter_plot/app.js b/examples/scatter_plot/app.js index dd332c1..e1f6284 100644 --- a/examples/scatter_plot/app.js +++ b/examples/scatter_plot/app.js @@ -1,6 +1,8 @@ +// dimensions for svg var WIDTH = 800; var HEIGHT = 600; +// run data var runs = [ { id: 1, @@ -19,66 +21,90 @@ var runs = [ } ]; - +// set dimensions of outer SVG d3.select('#container') .style('width', WIDTH) .style('height', HEIGHT); -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(); -xScale.range([0,WIDTH]); -var xDomain = d3.extent(runs, function(datum, index){ - return parseTime(datum.date); +var parseTime = d3.timeParse("%B%e, %Y at %-I:%M%p"); //use this to convert strings to dates +var formatTime = d3.timeFormat("%B%e, %Y at %-I:%M%p"); //use this to convert dates to strings +var xScale = d3.scaleTime(); //create the scale used to convert dates to x position values +xScale.range([0,WIDTH]); //set visual range of xScale to be 0 -> 800 +var xDomain = d3.extent(runs, function(datum, index){ //create array containing min/max date values for run data + return parseTime(datum.date); //use parseTime to convert string data value to data object }); -xScale.domain(xDomain); - -var yScale = d3.scaleLinear(); //create the scale -yScale.range([HEIGHT, 0]); //set the visual range (e.g. 600 to 0) +xScale.domain(xDomain);//set domain of xScale to min/max values created by d3.extent in last step + +var yScale = d3.scaleLinear(); //create the scale used to convert distances run to y position values +// set the visual range to 600 -> 0 +// remember 600 will map to a low run distance value and 0 will map to a high run distance value +// we do this because y starts at 0 at the top of the SVG and increases in value as we move down the SVG +yScale.range([HEIGHT, 0]); + //create array containing min/max distance values for run data var yDomain = d3.extent(runs, function(datum, index){ return datum.distance; //compare distance properties of each item in the data array }) -yScale.domain(yDomain); +yScale.domain(yDomain); //set domain of yScale to min/max values created by d3.extent in the last step + +//render function which creates the circles and attaches event handlers to them var render = function(){ - var circles = d3.select('#points') - .selectAll('circle') + //circles var for holding our circle selction + var circles = d3.select('#points') //first select #points so we have somewhere to append circles later + .selectAll('circle') //now select all circles even if none exist + //attach data to the circles and set up each circle's id according to the following function .data(runs, function(datum){ - return datum.id + return datum.id //use each datum's id property as the id for the circle it's being attached to }); + //find all data elements not attached to circles and create a circle for each one circles.enter().append('circle'); + //remove all extra circles that are not attached to data circles.exit().remove(); - d3.selectAll('circle') - .attr('cy', function(datum, index){ + d3.selectAll('circle') //select all circles + .attr('cy', function(datum, index){ //loop through each circle and set its cy according to the follwing function + //find the distance property of the circle's associated datum + //convert that distance value from a numeric value to a visual point on the SVG using yScale 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 + d3.selectAll('circle') //select all circles + .attr('cx', function(datum, index){ //loop through each circle and set its cx according to the follwing function + //find the date property of the circle's associated datum + //convert that date value from a string value to a date using parseTime + //convert that date object to a visual point on the SVG using xScale + return xScale(parseTime(datum.date)); }); - //put this at the bottom of the render function, so that click handlers are attached when the circle is created + //put this at the bottom of the render function, so that click handlers are attached after the circles is created + //if you put it outside the render function, there will be no circles to attach click handlers to d3.selectAll('circle').on('click', function(datum, index){ - d3.event.stopPropagation(); //stop click event from propagating to the SVG element and creating a run - runs = runs.filter(function(run, index){ //create a new array that has removed the run with the correct id. Set it to the runs var - return run.id != datum.id; + d3.event.stopPropagation(); //stop click event from propagating to the SVG element and creating a run accidentally + //create a new array that has removed the run with the correct id. Set it to the runs var + runs = runs.filter(function(run, index){ + return run.id != datum.id; //keep all elements in the 'runs' array that do not have the id of the cirlce that was clicked }); render(); //re-render dots createTable(); //re-render table }); + + //function to be called once dragging a circle is complette var dragEnd = function(datum){ + //find x/y of where the click happened var x = d3.event.x; var y = d3.event.y; - var date = xScale.invert(x); + //convert those x/y values to date/distance values + var date = xScale.invert(x); //note date is Date object. We'll convert it to a properly formatted string later var distance = yScale.invert(y); - datum.date = formatTime(date); + //adjust the date/distance values on the datum associated with the circle that was dragged + datum.date = formatTime(date); //use formatTime to turn the date object into a string datum.distance = distance; - createTable(); + createTable();//re-render the table } + + // function to be called while the user drags a circle var drag = function(datum){ var x = d3.event.x; //get current x position of the cursor var y = d3.event.y; //get current y position of the cursor @@ -86,46 +112,67 @@ var render = function(){ d3.select(this).attr('cy', y); //change the dragged element's cy attribute to whatever the y position of the cursor is } var dragBehavior = d3.drag() //create a drag behavior - .on('drag', drag) //call the "drag" function (the 2nd param) each time the user moves the cursor before releasing the mouse button. The "drag" function is defined above - .on('end', dragEnd); //dragEnd is a reference to a function we haven't created yet + //call the "drag" function (the 2nd param) each time the user moves the cursor before releasing the mouse button. + //The "drag" function is defined above + .on('drag', drag) + //call the "dragEnd" function (the 2nd param) once the user releases the "mouse button" + .on('end', dragEnd); d3.selectAll('circle').call(dragBehavior); //attach the dragBehavior behavior to all elements } +//render circles on page load render(); +//create a bottomAxis generator. Pass the xScale so it knows how to label the axis var bottomAxis = d3.axisBottom(xScale); -d3.select('#container') - .append('g') +d3.select('#container') //select the outer SVG + .append('g') //append a to it .attr('id', 'x-axis') //add an id - .call(bottomAxis) - .attr('transform', 'translate(0,'+HEIGHT+')'); + .call(bottomAxis) //call the axis generator on that so that an axis is generated within it + .attr('transform', 'translate(0,'+HEIGHT+')'); //move the axis to the bottom of the SVG +//create a leftAxis generator. Pass the yScale so it knows how to label the axis var leftAxis = d3.axisLeft(yScale); -d3.select('#container') - .append('g') +d3.select('#container') //select the outer SVG + .append('g') //append a to it .attr('id', 'y-axis') //add an id - .call(leftAxis); + .call(leftAxis); //call the axis generator on that so that an axis is generated within it +//define the createTable function here var createTable = function(){ d3.select('tbody').html(''); //clear out all rows from the table + //loop through each element in the runs array for (var i = 0; i < runs.length; i++) { - var row = d3.select('tbody').append('tr'); + var row = d3.select('tbody').append('tr');//append a tr to the tbody element + //create cells for id, date, and distance and add the values appropriately row.append('td').html(runs[i].id); row.append('td').html(runs[i].date); row.append('td').html(runs[i].distance); } } +//render the table on page load createTable(); +//create a click handler on the main SVG, allowing the user to create new runs by click anywhere in it d3.select('#container').on('click', function(){ - var x = lastTransform.invertX(d3.event.offsetX); - var y = lastTransform.invertY(d3.event.offsetY); + + //create x and y vars to the x/y values of the point that the user clicked + var x = d3.event.offsetX; + var y = d3.event.offsetY; + + //if a transform occurred (zoom/pan), adjust the x/y vars to take this into account + if(lastTransform !== null){ + x = lastTransform.invertX(d3.event.offsetX); + y = lastTransform.invertY(d3.event.offsetY); + } 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.length > 0 ) ? runs[runs.length-1].id+1 : 1, //generate a new id by adding 1 to the last run's id + //generate a new id by adding 1 to the last run's id + //if no runs exist, set the id to 1 + id: ( runs.length > 0 ) ? runs[runs.length-1].id+1 : 1, date: formatTime(date), //format the date object created above to a string distance: distance //add the distance } @@ -134,16 +181,20 @@ d3.select('#container').on('click', function(){ render(); //add this line }); +//create a var to hold any zoom/pan transformations that occur var lastTransform = null; + +//function to be called each to a user zooms/pans var zoomCallback = function(){ - lastTransform = d3.event.transform; //add this - d3.select('#points').attr("transform", d3.event.transform); - d3.select('#x-axis') + lastTransform = d3.event.transform; //save the transformation that just happened + d3.select('#points').attr("transform", d3.event.transform); //transform the appropriately + d3.select('#x-axis') //adjust the values in the x axis .call(bottomAxis.scale(d3.event.transform.rescaleX(xScale))); - d3.select('#y-axis') + d3.select('#y-axis') //adjust the values in the y axis .call(leftAxis.scale(d3.event.transform.rescaleY(yScale))); } +//set up the zoom behavior generator var zoom = d3.zoom() .on('zoom', zoomCallback); -d3.select('#container').call(zoom); +d3.select('#container').call(zoom); //attach the zoom behavior to the #container SVG diff --git a/examples/scatter_plot/index.html b/examples/scatter_plot/index.html index 42f3849..2c2f604 100644 --- a/examples/scatter_plot/index.html +++ b/examples/scatter_plot/index.html @@ -6,11 +6,15 @@ + + + + @@ -22,6 +26,7 @@
+