From 205617ecb36a09d0868913e34521f330e7e59c4d Mon Sep 17 00:00:00 2001 From: Matt Huntington Date: Fri, 9 Sep 2022 17:17:56 -0400 Subject: [PATCH] CRUD works --- app.css | 28 +++++++++++ app.js | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 24 ++++++++++ notes.txt | 5 ++ 4 files changed, 194 insertions(+) create mode 100644 app.css create mode 100644 app.js create mode 100644 index.html create mode 100644 notes.txt diff --git a/app.css b/app.css new file mode 100644 index 0000000..d502df9 --- /dev/null +++ b/app.css @@ -0,0 +1,28 @@ +circle { + r:5; + fill: black; + transition: r 0.5s linear, fill 0.5s linear; /* add this transition to original code */ + +} +svg { + overflow:visible; + margin-bottom: 50px; +} +body { + margin: 20px 40px; +} +table, th, td { + border: 1px solid black; +} +th, td { + padding:10px; + text-align: center; +} +/* add this css for the hover state */ +circle:hover { + r:10; + fill: blue; +} +circle:active { + fill: red; +} diff --git a/app.js b/app.js new file mode 100644 index 0000000..1ce8712 --- /dev/null +++ b/app.js @@ -0,0 +1,137 @@ +const WIDTH = 800; +const HEIGHT = 600; +let runs = [ + { + id: 1, + date: 'October 1, 2017 at 4:00PM', + distance: 5.2 + }, + { + id: 2, + date: 'October 2, 2017 at 5:00PM', + distance: 7.0725 + }, + { + id: 3, + date: 'October 3, 2017 at 6:00PM', + distance: 8.7 + } +]; + +d3.select('svg') + .style('width', WIDTH) + .style('height', HEIGHT); + +const parseTime = d3.timeParse("%B%e, %Y at %-I:%M%p"); +const formatTime = d3.timeFormat("%B%e, %Y at %-I:%M%p"); +const xScale = d3.scaleTime(); +xScale.range([0,WIDTH]); +const xDomain = d3.extent(runs, (datum, index) => { + return parseTime(datum.date); +}); +xScale.domain(xDomain); + +const yScale = d3.scaleLinear(); //create the scale +yScale.range([HEIGHT, 0]); //set the visual range (e.g. 600 to 0) +const yDomain = d3.extent(runs, (datum, index) => { + return datum.distance; //compare distance properties of each item in the data array +}) +yScale.domain(yDomain); + +const render = () => { + + //adjust the code at the top of your render function + d3.select('#points').html(''); //clear out all circles when rendering + d3.select('#points').selectAll('circle') //add circles to #points group, not svg + .data(runs) + .enter() + .append('circle'); + + d3.selectAll('circle').data(runs) + .attr('cy', (datum, index) => { + return yScale(datum.distance); + }); + + d3.selectAll('circle') + .attr('cx', (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 + }); + + //put this at the bottom of the render function, so that click handlers are attached when the circle is created + d3.selectAll('circle').on('click', (event, datum) => { + event.stopPropagation(); //stop click event from propagating to the SVG element and creating a run + runs = runs.filter((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; + }); + render(); //re-render dots + createTable(); //re-render table + }); + + const drag = function(event, datum) { + const x = event.x; + const y = event.y; + d3.select(this).attr('cx', x); + d3.select(this).attr('cy', y); + } + + const dragEnd = (event, datum) => { + const x = event.x; + const y = event.y; + + const date = xScale.invert(x); + const distance = yScale.invert(y); + + datum.date = formatTime(date); + datum.distance = distance; + createTable(); + } + + const dragBehavior = d3.drag() + .on('drag', drag) + .on('end', dragEnd); + d3.selectAll('circle').call(dragBehavior); + +} + +render(); + +const bottomAxis = d3.axisBottom(xScale); //pass the appropriate scale in as a parameter +d3.select('svg') + .append('g') //put everything inside a group + .call(bottomAxis) //generate the axis within the group + .attr('transform', 'translate(0,'+HEIGHT+')'); //move it to the bottom + +const leftAxis = d3.axisLeft(yScale); +d3.select('svg') + .append('g') + .call(leftAxis); //no need to transform, since it's placed correctly initially + +const createTable = () => { + d3.select('tbody').html(''); //clear out all rows from the table + for (let i = 0; i < runs.length; i++) { + const row = d3.select('tbody').append('tr'); + row.append('td').html(runs[i].id); + row.append('td').html(runs[i].date); + row.append('td').html(runs[i].distance); + } +} + +createTable(); + +d3.select('svg').on('click', (event) => { + const x = event.offsetX; //gets the x position of the mouse relative to the svg element + const y = event.offsetY; //gets the y position of the mouse relative to the svg element + + const date = xScale.invert(x) //get a date value from the visual point that we clicked on + const distance = yScale.invert(y); //get a numeric distance value from the visual point that we clicked on + + const newRun = { //create a new "run" object + id: ( runs.length > 0 ) ? runs[runs.length-1].id+1 : 1, //add this line + 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 +}); + diff --git a/index.html b/index.html new file mode 100644 index 0000000..6038357 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + +
iddatedistance
+ + + + diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..b60a353 --- /dev/null +++ b/notes.txt @@ -0,0 +1,5 @@ +- D3.md + - check var vs const/let declarations + - check function vs ()=>{} declarations +- SCATTER_PLOT.md + - https://github.com/mahuntington/d3-notes/blob/notes/SCATTER_PLOT.md#drag-an-element