script.js
import drawTrack from './drawTrack.js'
import drawCars from './drawCars.js'
const container = d3.select('.circuit')
const { g, getCoords } = drawTrack({ container, track: circuitTracks[0] })
let state = {
car: {
position: 1
},
time: {
start: 0,
now: 0
}
}
const formatTime = d3.timeFormat('%M:%S:%L')
const updateCounter = () => {
const counterDiv = document.querySelector('.counter')
counterDiv.querySelector('.lap .value').textContent = Math.floor(
state.car.position
)
counterDiv.querySelector('.time .value').textContent = formatTime(
state.time.now
)
}
const draw = () => {
drawCars({ g, getCoords, cars: [state.car] })
updateCounter()
}
draw()
const tick = elapsed => {
const { car, time } = state
const newPosition = car.position + 0.0003
const isNewLap =
car.position === 1 || Math.floor(car.position) !== Math.floor(newPosition)
const start = isNewLap ? elapsed : time.start
state = {
...state,
car: {
...car,
position: newPosition
},
time: {
...time,
start,
now: elapsed - start
}
}
}
d3.timer(elapsed => {
tick(elapsed)
draw()
})
dist.js
!function(n){function t(a){if(e[a])return e[a].exports;var c=e[a]={i:a,l:!1,exports:{}};return n[a].call(c.exports,c,c.exports,t),c.l=!0,c.exports}var e={};t.m=n,t.c=e,t.i=function(n){return n},t.d=function(n,e,a){t.o(n,e)||Object.defineProperty(n,e,{configurable:!1,enumerable:!0,get:a})},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},t.p="",t(t.s=2)}([function(module,exports,__webpack_require__){"use strict";eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nvar drawCars = function drawCars(_ref) {\n var g = _ref.g,\n cars = _ref.cars,\n getCoords = _ref.getCoords;\n\n // join\n var circles = g.selectAll('circle.car').data(cars);\n\n // update\n circles.attr('transform', function (d) {\n return 'translate(' + getCoords(d.position) + ')';\n });\n\n // enter\n circles.enter().append('circle').attr('class', 'car').attr('cx', 0).attr('cy', 0).attr('r', 4).attr('transform', function (d) {\n return 'translate(' + getCoords(d.position) + ')';\n });\n\n circles.exit().remove();\n};\n\nexports.default = drawCars;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9kcmF3Q2Fycy5qcz9hMDAwIl0sInNvdXJjZXNDb250ZW50IjpbImNvbnN0IGRyYXdDYXJzID0gKHsgZywgY2FycywgZ2V0Q29vcmRzIH0pID0+IHtcbiAgLy8gam9pblxuICBjb25zdCBjaXJjbGVzID0gZy5zZWxlY3RBbGwoJ2NpcmNsZS5jYXInKS5kYXRhKGNhcnMpXG5cbiAgLy8gdXBkYXRlXG4gIGNpcmNsZXNcbiAgICAuYXR0cigndHJhbnNmb3JtJywgZCA9PiBgdHJhbnNsYXRlKCR7Z2V0Q29vcmRzKGQucG9zaXRpb24pfSlgKVxuXG4gIC8vIGVudGVyXG4gIGNpcmNsZXNcbiAgICAuZW50ZXIoKVxuICAgIC5hcHBlbmQoJ2NpcmNsZScpXG4gICAgLmF0dHIoJ2NsYXNzJywgJ2NhcicpXG4gICAgLmF0dHIoJ2N4JywgMClcbiAgICAuYXR0cignY3knLCAwKVxuICAgIC5hdHRyKCdyJywgNClcbiAgICAuYXR0cigndHJhbnNmb3JtJywgZCA9PiBgdHJhbnNsYXRlKCR7Z2V0Q29vcmRzKGQucG9zaXRpb24pfSlgKVxuXG4gIGNpcmNsZXMuZXhpdCgpLnJlbW92ZSgpXG59XG5cbmV4cG9ydCBkZWZhdWx0IGRyYXdDYXJzXG5cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gZHJhd0NhcnMuanMiXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBT0E7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///0\n")},function(module,exports,__webpack_require__){"use strict";eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nvar drawTrack = function drawTrack(_ref) {\n var container = _ref.container,\n track = _ref.track;\n\n var margin = 10;\n var dimension = container.node().offsetWidth - margin * 2;\n\n var xExtent = d3.extent(track, function (d) {\n return d.x;\n });\n var x = d3.scaleLinear().domain(xExtent);\n\n var yExtent = d3.extent(track, function (d) {\n return d.y;\n });\n var y = d3.scaleLinear().domain(yExtent);\n\n var aspect = (xExtent[1] - xExtent[0]) / (yExtent[1] - yExtent[0]);\n\n var width = Math.min(dimension * aspect, dimension);\n var height = Math.min(dimension / aspect, dimension);\n\n x.range([0, width]);\n y.range([0, height]);\n\n var svg = container.append('svg').attr('width', width + 2 * margin).attr('height', height + 2 * margin);\n\n var g = svg.append('g').attr('transform', 'translate(' + margin + ', ' + margin + ')');\n\n var line = d3.line().curve(d3.curveBasis).x(function (d) {\n return x(d.x);\n }).y(function (d) {\n return y(d.y);\n });\n\n var path = g.append('path').attr('class', 'track').datum(track).attr('d', line).node();\n\n var totalLength = path.getTotalLength();\n\n var getCoords = function getCoords(pct) {\n var p = path.getPointAtLength(totalLength * (pct % 1));\n return [p.x, p.y];\n };\n\n var delta = 0.005;\n var before = getCoords(1 - delta);\n var zero = getCoords(0);\n var after = getCoords(delta);\n\n g.append('line').attr('class', 'finish-line').attr('x1', before[0]).attr('y1', before[1]).attr('x2', after[0]).attr('y2', after[1]).attr('transform', 'rotate(90, ' + zero + ')');\n\n return { g: g, getCoords: getCoords };\n};\n\nexports.default = drawTrack;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9kcmF3VHJhY2suanM/MTY1OSJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBkcmF3VHJhY2sgPSAoeyBjb250YWluZXIsIHRyYWNrIH0pID0+IHtcbiAgY29uc3QgbWFyZ2luID0gMTBcbiAgY29uc3QgZGltZW5zaW9uID0gY29udGFpbmVyLm5vZGUoKS5vZmZzZXRXaWR0aCAtIG1hcmdpbiAqIDJcblxuICBjb25zdCB4RXh0ZW50ID0gZDMuZXh0ZW50KHRyYWNrLCBkID0+IGQueClcbiAgY29uc3QgeCA9IGQzLnNjYWxlTGluZWFyKCkuZG9tYWluKHhFeHRlbnQpXG5cbiAgY29uc3QgeUV4dGVudCA9IGQzLmV4dGVudCh0cmFjaywgZCA9PiBkLnkpXG4gIGNvbnN0IHkgPSBkMy5zY2FsZUxpbmVhcigpLmRvbWFpbih5RXh0ZW50KVxuXG4gIGNvbnN0IGFzcGVjdCA9ICh4RXh0ZW50WzFdIC0geEV4dGVudFswXSkgLyAoeUV4dGVudFsxXSAtIHlFeHRlbnRbMF0pXG5cbiAgY29uc3Qgd2lkdGggPSBNYXRoLm1pbihkaW1lbnNpb24gKiBhc3BlY3QsIGRpbWVuc2lvbilcbiAgY29uc3QgaGVpZ2h0ID0gTWF0aC5taW4oZGltZW5zaW9uIC8gYXNwZWN0LCBkaW1lbnNpb24pXG5cbiAgeC5yYW5nZShbMCwgd2lkdGhdKVxuICB5LnJhbmdlKFswLCBoZWlnaHRdKVxuXG4gIGNvbnN0IHN2ZyA9IGNvbnRhaW5lclxuICAgIC5hcHBlbmQoJ3N2ZycpXG4gICAgLmF0dHIoJ3dpZHRoJywgd2lkdGggKyAyICogbWFyZ2luKVxuICAgIC5hdHRyKCdoZWlnaHQnLCBoZWlnaHQgKyAyICogbWFyZ2luKVxuXG4gIGNvbnN0IGcgPSBzdmcuYXBwZW5kKCdnJykuYXR0cigndHJhbnNmb3JtJywgYHRyYW5zbGF0ZSgke21hcmdpbn0sICR7bWFyZ2lufSlgKVxuXG4gIGNvbnN0IGxpbmUgPSBkM1xuICAgIC5saW5lKClcbiAgICAuY3VydmUoZDMuY3VydmVCYXNpcylcbiAgICAueChkID0+IHgoZC54KSlcbiAgICAueShkID0+IHkoZC55KSlcblxuICBjb25zdCBwYXRoID0gZ1xuICAgIC5hcHBlbmQoJ3BhdGgnKVxuICAgIC5hdHRyKCdjbGFzcycsICd0cmFjaycpXG4gICAgLmRhdHVtKHRyYWNrKVxuICAgIC5hdHRyKCdkJywgbGluZSlcbiAgICAubm9kZSgpXG5cbiAgY29uc3QgdG90YWxMZW5ndGggPSBwYXRoLmdldFRvdGFsTGVuZ3RoKClcblxuICBjb25zdCBnZXRDb29yZHMgPSBwY3QgPT4ge1xuICAgIGNvbnN0IHAgPSBwYXRoLmdldFBvaW50QXRMZW5ndGgodG90YWxMZW5ndGggKiAocGN0ICUgMSkpXG4gICAgcmV0dXJuIFtwLngsIHAueV1cbiAgfVxuXG4gIGNvbnN0IGRlbHRhID0gMC4wMDVcbiAgY29uc3QgYmVmb3JlID0gZ2V0Q29vcmRzKDEgLSBkZWx0YSlcbiAgY29uc3QgemVybyA9IGdldENvb3JkcygwKVxuICBjb25zdCBhZnRlciA9IGdldENvb3JkcyhkZWx0YSlcblxuICBnXG4gICAgLmFwcGVuZCgnbGluZScpXG4gICAgLmF0dHIoJ2NsYXNzJywgJ2ZpbmlzaC1saW5lJylcbiAgICAuYXR0cigneDEnLCBiZWZvcmVbMF0pXG4gICAgLmF0dHIoJ3kxJywgYmVmb3JlWzFdKVxuICAgIC5hdHRyKCd4MicsIGFmdGVyWzBdKVxuICAgIC5hdHRyKCd5MicsIGFmdGVyWzFdKVxuICAgIC5hdHRyKCd0cmFuc2Zvcm0nLCBgcm90YXRlKDkwLCAke3plcm99KWApXG5cbiAgcmV0dXJuIHsgZywgZ2V0Q29vcmRzIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgZHJhd1RyYWNrXG5cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gZHJhd1RyYWNrLmpzIl0sIm1hcHBpbmdzIjoiOzs7OztBQUFBO0FBQUE7QUFBQTtBQUNBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUlBO0FBQ0E7QUFDQTtBQUdBO0FBQUE7QUFDQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBTUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQVFBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///1\n")},function(module,exports,__webpack_require__){"use strict";eval("\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _drawTrack2 = __webpack_require__(1);\n\nvar _drawTrack3 = _interopRequireDefault(_drawTrack2);\n\nvar _drawCars = __webpack_require__(0);\n\nvar _drawCars2 = _interopRequireDefault(_drawCars);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar container = d3.select('.circuit');\n\n// Draw the track.\n\nvar _drawTrack = (0, _drawTrack3.default)({ container: container, track: circuitTracks[0] }),\n g = _drawTrack.g,\n getCoords = _drawTrack.getCoords;\n\n// Create the state.\n\n\nvar state = {\n car: {\n position: 1\n },\n time: {\n start: 0,\n now: 0\n }\n};\n\nvar formatTime = d3.timeFormat('%M:%S:%L');\n\nvar updateCounter = function updateCounter() {\n var counterDiv = document.querySelector('.counter');\n counterDiv.querySelector('.lap .value').textContent = Math.floor(state.car.position);\n counterDiv.querySelector('.time .value').textContent = formatTime(state.time.now);\n};\n\nvar draw = function draw() {\n // Draw the car.\n (0, _drawCars2.default)({ g: g, getCoords: getCoords, cars: [state.car] });\n\n // Update the counter\n updateCounter();\n};\n\ndraw();\n\nvar tick = function tick(elapsed) {\n var _state = state,\n car = _state.car,\n time = _state.time;\n\n // Move the car.\n\n var newPosition = car.position + 0.0003;\n\n // Did we just start a new lap? (i.e. cross the finish line)\n var isNewLap = car.position === 1 || Math.floor(car.position) !== Math.floor(newPosition);\n\n // If we started a new lap, reset the time.\n var start = isNewLap ? elapsed : time.start;\n\n // Update the state.\n state = _extends({}, state, {\n car: _extends({}, car, {\n position: newPosition\n }),\n time: _extends({}, time, {\n start: start,\n now: elapsed - start\n })\n });\n};\n\nd3.timer(function (elapsed) {\n tick(elapsed);\n draw();\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMi5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9zY3JpcHQuanM/OWE5NSJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZHJhd1RyYWNrIGZyb20gJy4vZHJhd1RyYWNrLmpzJ1xuaW1wb3J0IGRyYXdDYXJzIGZyb20gJy4vZHJhd0NhcnMuanMnXG5cbmNvbnN0IGNvbnRhaW5lciA9IGQzLnNlbGVjdCgnLmNpcmN1aXQnKVxuXG4vLyBEcmF3IHRoZSB0cmFjay5cbmNvbnN0IHsgZywgZ2V0Q29vcmRzIH0gPSBkcmF3VHJhY2soeyBjb250YWluZXIsIHRyYWNrOiBjaXJjdWl0VHJhY2tzWzBdIH0pXG5cbi8vIENyZWF0ZSB0aGUgc3RhdGUuXG5sZXQgc3RhdGUgPSB7XG4gIGNhcjoge1xuICAgIHBvc2l0aW9uOiAxXG4gIH0sXG4gIHRpbWU6IHtcbiAgICBzdGFydDogMCxcbiAgICBub3c6IDBcbiAgfVxufVxuXG5jb25zdCBmb3JtYXRUaW1lID0gZDMudGltZUZvcm1hdCgnJU06JVM6JUwnKVxuXG5jb25zdCB1cGRhdGVDb3VudGVyID0gKCkgPT4ge1xuICBjb25zdCBjb3VudGVyRGl2ID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcignLmNvdW50ZXInKVxuICBjb3VudGVyRGl2LnF1ZXJ5U2VsZWN0b3IoJy5sYXAgLnZhbHVlJykudGV4dENvbnRlbnQgPSBNYXRoLmZsb29yKFxuICAgIHN0YXRlLmNhci5wb3NpdGlvblxuICApXG4gIGNvdW50ZXJEaXYucXVlcnlTZWxlY3RvcignLnRpbWUgLnZhbHVlJykudGV4dENvbnRlbnQgPSBmb3JtYXRUaW1lKFxuICAgIHN0YXRlLnRpbWUubm93XG4gIClcbn1cblxuY29uc3QgZHJhdyA9ICgpID0+IHtcbiAgLy8gRHJhdyB0aGUgY2FyLlxuICBkcmF3Q2Fycyh7IGcsIGdldENvb3JkcywgY2FyczogW3N0YXRlLmNhcl0gfSlcblxuICAvLyBVcGRhdGUgdGhlIGNvdW50ZXJcbiAgdXBkYXRlQ291bnRlcigpXG59XG5cbmRyYXcoKVxuXG5jb25zdCB0aWNrID0gZWxhcHNlZCA9PiB7XG4gIGNvbnN0IHsgY2FyLCB0aW1lIH0gPSBzdGF0ZVxuXG4gIC8vIE1vdmUgdGhlIGNhci5cbiAgY29uc3QgbmV3UG9zaXRpb24gPSBjYXIucG9zaXRpb24gKyAwLjAwMDNcblxuICAvLyBEaWQgd2UganVzdCBzdGFydCBhIG5ldyBsYXA/IChpLmUuIGNyb3NzIHRoZSBmaW5pc2ggbGluZSlcbiAgY29uc3QgaXNOZXdMYXAgPVxuICAgIGNhci5wb3NpdGlvbiA9PT0gMSB8fCBNYXRoLmZsb29yKGNhci5wb3NpdGlvbikgIT09IE1hdGguZmxvb3IobmV3UG9zaXRpb24pXG5cbiAgLy8gSWYgd2Ugc3RhcnRlZCBhIG5ldyBsYXAsIHJlc2V0IHRoZSB0aW1lLlxuICBjb25zdCBzdGFydCA9IGlzTmV3TGFwID8gZWxhcHNlZCA6IHRpbWUuc3RhcnRcblxuICAvLyBVcGRhdGUgdGhlIHN0YXRlLlxuICBzdGF0ZSA9IHtcbiAgICAuLi5zdGF0ZSxcbiAgICBjYXI6IHtcbiAgICAgIC4uLmNhcixcbiAgICAgIHBvc2l0aW9uOiBuZXdQb3NpdGlvblxuICAgIH0sXG4gICAgdGltZToge1xuICAgICAgLi4udGltZSxcbiAgICAgIHN0YXJ0LFxuICAgICAgbm93OiBlbGFwc2VkIC0gc3RhcnRcbiAgICB9XG4gIH1cbn1cblxuZDMudGltZXIoZWxhcHNlZCA9PiB7XG4gIHRpY2soZWxhcHNlZClcbiAgZHJhdygpXG59KVxuXG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIHNjcmlwdC5qcyJdLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0E7OztBQUFBO0FBQ0E7Ozs7O0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQURBO0FBQ0E7QUFDQTtBQURBO0FBR0E7QUFDQTtBQUNBO0FBRkE7QUFKQTtBQUNBO0FBU0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUdBO0FBR0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUVBO0FBQ0E7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUVBO0FBRUE7QUFGQTtBQUlBO0FBRUE7QUFDQTtBQUhBO0FBTkE7QUFZQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///2\n")}]);
drawTrack.js
const drawTrack = ({ container, track }) => {
const margin = 10
const dimension = container.node().offsetWidth - margin * 2
const xExtent = d3.extent(track, d => d.x)
const x = d3.scaleLinear().domain(xExtent)
const yExtent = d3.extent(track, d => d.y)
const y = d3.scaleLinear().domain(yExtent)
const aspect = (xExtent[1] - xExtent[0]) / (yExtent[1] - yExtent[0])
const width = Math.min(dimension * aspect, dimension)
const height = Math.min(dimension / aspect, dimension)
x.range([0, width])
y.range([0, height])
const svg = container
.append('svg')
.attr('width', width + 2 * margin)
.attr('height', height + 2 * margin)
const g = svg.append('g').attr('transform', `translate(${margin}, ${margin})`)
const line = d3
.line()
.curve(d3.curveBasis)
.x(d => x(d.x))
.y(d => y(d.y))
const path = g
.append('path')
.attr('class', 'track')
.datum(track)
.attr('d', line)
.node()
const totalLength = path.getTotalLength()
const getCoords = pct => {
const p = path.getPointAtLength(totalLength * (pct % 1))
return [p.x, p.y]
}
const delta = 0.005
const before = getCoords(1 - delta)
const zero = getCoords(0)
const after = getCoords(delta)
g
.append('line')
.attr('class', 'finish-line')
.attr('x1', before[0])
.attr('y1', before[1])
.attr('x2', after[0])
.attr('y2', after[1])
.attr('transform', `rotate(90, ${zero})`)
return { g, getCoords }
}
export default drawTrack