-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from sharadbhat/alex
d3 classes used for visualizations
- Loading branch information
Showing
6 changed files
with
522 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
class CumulativeRecordedLaps{ | ||
constructor(divId, lapData, driverData, defaultRaceId='845', dependencies){ | ||
this.divId = divId; | ||
this.dependencies = dependencies; | ||
|
||
//set SVG attributes/groups | ||
d3.select('#' + this.divId).attr("style", "padding: 0"); | ||
this.SVGWidth = parseInt(d3.select('#' + this.divId).style("width")); | ||
this.SVGHeight = this.SVGWidth/2; | ||
this.scaleFactor = (this.SVGWidth/759); | ||
d3.select('#' + this.divId) | ||
.attr("width", this.SVGWidth) | ||
.attr("height", this.SVGHeight); | ||
d3.select('#' + this.divId).append("g").attr("id", "cumulativeLapGroup"); | ||
d3.select('#' + this.divId).append("g").attr("id", "cumulativeLapXAxis"); | ||
d3.select('#' + this.divId).append("g").attr("id", "cumulativeLapYAxis"); | ||
d3.select('#' + this.divId).append("g").attr("id", "cumulativeLapLine"); | ||
d3.select('#' + this.divId).on("mousemove", function(pos){ | ||
this.dependencies.recordedLaps.updateSlider(this.dependencies.recordedLaps.SVGWidth / this.SVGWidth * pos.offsetX); | ||
this.updateLine(pos.offsetX) | ||
}.bind(this)); | ||
|
||
//set scales | ||
this.xScale = null; | ||
this.yScale = null; | ||
|
||
//grouped data items | ||
this.groupedLapData = d3.group(lapData, d => d.raceId); | ||
this.groupedDriverData = d3.group(driverData, d => d.driverId); | ||
this.currentData = null; | ||
|
||
//update chart data | ||
this.updateDataPoints(defaultRaceId); | ||
this.updateScatterPlot(); | ||
} | ||
|
||
updateDataPoints(raceId){ | ||
//modify currentData | ||
this.currentData = structuredClone(this.groupedLapData.get(raceId)); | ||
this.currentData = d3.flatGroup(this.currentData, d => d.driverId); | ||
|
||
//make cumulative | ||
for (let driver of this.currentData){ | ||
driver[1].forEach(function (e) { | ||
this.count = (this.count || 0) + parseInt(e.milliseconds); | ||
e.milliseconds = parseFloat(this.count); | ||
}, Object.create(null)); | ||
} | ||
} | ||
|
||
updateScatterPlot(){ | ||
let maxLap = d3.max(this.currentData, driverData => d3.max(driverData[1], d => parseFloat(d.lap))); | ||
let minLap = d3.min(this.currentData, driverData => d3.min(driverData[1], d => parseFloat(d.lap))); | ||
let maxLapTime = d3.max(this.currentData, driverData => d3.max(driverData[1], d => parseFloat(d.milliseconds))); | ||
let minLapTime = d3.min(this.currentData, driverData => d3.min(driverData[1], d => parseFloat(d.milliseconds))); | ||
|
||
//set scales | ||
this.xScale = d3.scaleLinear() | ||
.domain([minLap, maxLap]) | ||
.range([0, this.SVGWidth]); | ||
this.yScale = d3.scaleLinear() | ||
.domain([minLapTime, maxLapTime]) | ||
.range([this.SVGHeight, 0]); | ||
|
||
//set color mapping | ||
let colorScale = d3.scaleOrdinal() | ||
.domain(Array.from(this.groupedDriverData.keys())) | ||
.range(d3.schemeCategory10); | ||
|
||
//plot data | ||
let line = d3.line() | ||
.x(d => this.xScale(parseInt(d.lap))) | ||
.y(d => this.yScale(parseInt(d.milliseconds))); | ||
d3.select("#cumulativeLapGroup").selectAll("path").data(this.currentData).join( | ||
enter => enter | ||
.append("path") | ||
.attr("fill", "none") | ||
.attr("d", d => line(d[1])) | ||
.attr("stroke", d => colorScale(d[0])) | ||
.on("mousemove", function(pos, data){ | ||
console.log(pos, data); | ||
}.bind(this)), | ||
update => update | ||
.attr("d", d => line(d[1])) | ||
.attr("stroke", d => colorScale(d[0])), | ||
exit => exit | ||
.remove() | ||
); | ||
} | ||
|
||
updateLine(xValue){ | ||
let currentLap = xValue; | ||
d3.select('#' + this.divId).select("#cumulativeLapLine").selectAll("line").data([currentLap]).join( | ||
enter => enter | ||
.append("line") | ||
.attr("y1", 0) | ||
.attr("y2", this.SVGHeight) | ||
.attr("stroke", "black"), | ||
update => update | ||
.attr("x1", d => d) | ||
.attr("x2", d => d), | ||
exit => exit | ||
.remove() | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/*class RacerList{ | ||
constructor(divId, ) | ||
}*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
class RecordedLaps{ | ||
constructor(divId, listId, lapData, driverData, defaultRaceId='845', dependencies){ | ||
this.divId = divId; | ||
this.listId = listId; | ||
this.ANIMATION_DURATION = 300; | ||
|
||
//set SVG attributes/groups | ||
d3.select('#' + this.divId).attr("style", "padding: 0"); | ||
this.SVGWidth = parseInt(d3.select('#' + this.divId).style("width")); | ||
this.SVGHeight = this.SVGWidth; | ||
this.scaleFactor = (this.SVGWidth/759); | ||
d3.select('#' + this.divId) | ||
.attr("width", this.SVGWidth) | ||
.attr("height", this.SVGHeight); | ||
d3.select('#' + this.divId).append("rect").attr("width", "100%").attr("height", "100%").attr("fill","#f5f5f5"); | ||
d3.select('#' + this.divId).append("g").attr("id", "scatterGroup"); | ||
d3.select('#' + this.divId).append("g").attr("id", "scatterXAxis"); | ||
d3.select('#' + this.divId).append("g").attr("id", "scatterYAxis"); | ||
d3.select('#' + this.divId).append("g").attr("id", "scatterSlider"); | ||
d3.select('#' + this.listId).append("g").attr("id", "currentRaceListText"); | ||
|
||
//set scales | ||
this.xScale = null; | ||
this.yScale = null; | ||
|
||
//grouped data items | ||
this.groupedLapData = d3.group(lapData, d => d.raceId); | ||
this.groupedDriverData = d3.group(driverData, d => d.driverId); | ||
this.currentData = null; | ||
|
||
//update chart data | ||
this.updateDataPoints(defaultRaceId); | ||
this.updateScatterPlot(); | ||
} | ||
|
||
updateDataPoints(raceId){ | ||
//modify currentData | ||
this.currentData = structuredClone(this.groupedLapData.get(raceId)); | ||
} | ||
|
||
updateSlider(xValue){ | ||
let currentLap = Math.ceil(this.xScale.invert(xValue)); | ||
|
||
//update slider | ||
d3.select('#' + this.divId).select("#scatterSlider").selectAll("rect").data([currentLap]).join( | ||
enter => enter | ||
.append("rect") | ||
.attr("id", "slideRect") | ||
.attr("fill", "black") | ||
.attr("opacity", "0.5") | ||
.attr("x", this.xScale(currentLap) - this.scaleFactor * 5) | ||
.attr("y", 0) | ||
.attr("height", this.SVGHeight) | ||
.attr("width", this.xScale(2) * this.scaleFactor), | ||
update => update | ||
.attr("x", this.xScale(currentLap) - this.scaleFactor * 5), | ||
exit => exit | ||
.remove() | ||
); | ||
|
||
//update list values | ||
let racersByLap = d3.filter(this.currentData, function(d){ | ||
return parseInt(d.lap) === currentLap; | ||
}.bind(this)); | ||
racersByLap = racersByLap.sort((a, b) => parseInt(a.position) - parseInt(b.position)); | ||
console.log(racersByLap); | ||
d3.select("#currentRaceListText").selectAll("g").data(racersByLap).join( | ||
function(enter){ | ||
let group = enter.append("g"); | ||
group.append("rect") | ||
.attr("x", 0) | ||
.attr("y", (d,i) => i * 17) | ||
.attr("height", "17") | ||
.attr("width", "100%") | ||
.attr("fill", "gray"),//colorScale(d.driverId)) | ||
group.append("text") | ||
.attr("x", 0) | ||
.attr("y", (d,i) => i * 17 + 16) | ||
.text(d => this.groupedDriverData.get(d.driverId)[0].driverRef + " " + d.position); | ||
}.bind(this), | ||
update => update | ||
.select("text") | ||
.text(d => this.groupedDriverData.get(d.driverId)[0].driverRef + " " + d.position), | ||
exit => exit | ||
.remove() | ||
); | ||
|
||
} | ||
|
||
updateScatterPlot(){ | ||
let maxLap = d3.max(this.currentData, d => parseInt(d.lap)); | ||
let minLap = d3.min(this.currentData, d => parseInt(d.lap)); | ||
let maxLapTime = d3.max(this.currentData, d => parseInt(d.milliseconds)); | ||
let minLapTime = d3.min(this.currentData, d => parseInt(d.milliseconds)); | ||
|
||
//set color mapping | ||
let colorScale = d3.scaleOrdinal() | ||
.domain(Array.from(this.groupedDriverData.keys())) | ||
.range(d3.schemeCategory10); | ||
|
||
//update scales | ||
this.xScale = d3.scaleLinear() | ||
.domain([minLap, maxLap]) | ||
.range([5 * this.scaleFactor, this.SVGWidth - 5 * this.scaleFactor]); | ||
this.yScale = d3.scaleLinear() | ||
.domain([minLapTime, maxLapTime]) | ||
.range([this.SVGHeight, 0]); | ||
|
||
//update slider | ||
d3.select('#' + this.divId).on("mousemove", function(pos){ | ||
this.updateSlider(pos.offsetX); | ||
}.bind(this)) | ||
|
||
//plot data | ||
d3.select("#scatterGroup").selectAll("circle").data(this.currentData).join( | ||
enter => enter | ||
.append("circle") | ||
.attr("cx", d => this.xScale(parseInt(d.lap))) | ||
.attr("cy", d => this.yScale(parseInt(d.milliseconds))) | ||
.attr("opacity", function(d){ | ||
let filteredData = d3.filter(this.currentData, score => score.lap === d.lap); | ||
let lapMedian = d3.median(filteredData, d1 => d1.milliseconds); | ||
let difference = Math.abs(lapMedian - Math.abs(d.milliseconds - parseInt(lapMedian))); | ||
|
||
let returnVal = Math.pow(1.0 * (difference / lapMedian), 64); | ||
return returnVal < 0.2 ? 0.2 : returnVal; | ||
}.bind(this)) | ||
.attr("fill", d => colorScale(d.driverId)) | ||
.attr("r", this.scaleFactor * 5), | ||
update => update | ||
.attr("cx", d => this.xScale(parseInt(d.lap))) | ||
.attr("cy", d => this.yScale(parseInt(d.milliseconds))) | ||
.attr("r", 0) | ||
.transition().duration(this.ANIMATION_DURATION) | ||
.attr("r", this.scaleFactor * 5), | ||
exit => exit | ||
.transition().duration(this.ANIMATION_DURATION) | ||
.attr("r", 0) | ||
.remove() | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
class WorldGrid{ | ||
constructor(divId, worldId, worldData, raceData, circuitData, defaultYear='2000'){ | ||
//grouped data | ||
this.raceData = d3.group(raceData, d => d.year); | ||
this.circuitData = d3.group(circuitData, d => d.circuitId); | ||
this.currentData = null; | ||
this.divId = divId; | ||
this.worldId = worldId; | ||
this.ANIMATION_DURATION = 300; | ||
|
||
//set SVG attributes/groups | ||
d3.select('#' + this.divId).attr("style", "padding: 0"); | ||
this.SVGWidth = parseInt(d3.select('#' + this.divId).style("width")); | ||
this.SVGHeight = this.SVGWidth/2; | ||
this.scaleFactor = (this.SVGWidth/759); | ||
d3.select('#' + this.divId) | ||
.attr("width", this.SVGWidth) | ||
.attr("height", this.SVGHeight) | ||
.append("g").attr("id", "tileGroup"); | ||
|
||
//create gradient | ||
let gradient = d3.select("#tileGroup").append("defs") | ||
.append("linearGradient") | ||
.attr("id", "gridGradient") | ||
.attr("gradientTransform", "rotate(90)"); | ||
gradient.append("stop") | ||
.attr("offset", "5%") | ||
.attr("stop-color", "#808080") | ||
gradient.append("stop") | ||
.attr("offset", "95%") | ||
.attr("stop-color", "#434343") | ||
|
||
//initialize data | ||
this.updateData(defaultYear); | ||
} | ||
|
||
updateData(currentYear){ | ||
this.updateDataPoints(currentYear); | ||
this.updateChart(); | ||
} | ||
|
||
updateDataPoints(currentYear){ | ||
//modify currentData | ||
let newCurrentData = new Array(); | ||
for (let race of this.raceData.get(currentYear)){ | ||
let appendedData = structuredClone(this.circuitData.get(race.circuitId)[0]); | ||
appendedData.raceId = race.raceId; | ||
newCurrentData.push(appendedData); | ||
} | ||
this.currentData = newCurrentData; | ||
} | ||
|
||
updateChart(){ | ||
let newLineVal = Math.ceil(Math.sqrt(this.currentData.length)); | ||
let widthScale = d3.scaleLinear() | ||
.domain([0, newLineVal]) | ||
.range([0, this.SVGWidth]); | ||
let heightScale = d3.scaleLinear() | ||
.domain([0, newLineVal]) | ||
.range([0, this.SVGHeight]); | ||
|
||
d3.select("#tileGroup").selectAll("svg").data(this.currentData).join( | ||
function(enter){ | ||
let SVG = enter.append("svg") | ||
.attr("x", (d,i) => widthScale((i % newLineVal))) | ||
.attr("y", (d,i) => heightScale((Math.floor(i / newLineVal)))) | ||
.attr("width", this.SVGWidth/newLineVal) | ||
.attr("height", this.SVGHeight/newLineVal); | ||
SVG.append("rect") | ||
.attr("width", "100%") | ||
.attr("height", "100%") | ||
.attr("rx", 10 * this.scaleFactor) | ||
.attr("ry", 10 * this.scaleFactor) | ||
.attr("fill", "url('#gridGradient')") | ||
.attr("stroke", "black") | ||
.on("mouseover", function(event, data){ | ||
d3.select('#' + this.worldId).select("#locationGroup").selectAll("circle") | ||
.transition().duration(this.ANIMATION_DURATION) | ||
.attr("r", function(d){ | ||
if (d.circuitId === data.circuitId) | ||
return 10 * this.scaleFactor; | ||
else | ||
return 5 * this.scaleFactor; | ||
}.bind(this)); | ||
d3.select(event.path[0]) | ||
.attr("fill", "white"); | ||
}.bind(this)) | ||
.on("mouseout", function(event){ | ||
d3.select('#' + this.worldId).select("#locationGroup").selectAll("circle") | ||
.transition().duration(this.ANIMATION_DURATION) | ||
.attr("r", 5 * this.scaleFactor); | ||
d3.select(event.path[0]) | ||
.attr("fill", "url('#gridGradient')"); | ||
}.bind(this)); | ||
SVG.append("text") | ||
.attr("text-anchor", "middle") | ||
.attr("dominant-baseline", "middle") | ||
.attr("pointer-events", "none") | ||
.attr("font-weight", "bold") | ||
.style("font-size", 15 * this.scaleFactor) | ||
.attr("x", "50%") | ||
.attr("y", "50%") | ||
.text(d => d.location); | ||
}.bind(this), | ||
update => update | ||
.select("text") | ||
.text(d => d.location), | ||
exit => exit | ||
.remove() | ||
); | ||
} | ||
} |
Oops, something went wrong.