From 906a3ca33d7f5bf1183af9ecec62376411f41bf1 Mon Sep 17 00:00:00 2001 From: David Harrigan Date: Tue, 9 Aug 2022 17:23:10 -0400 Subject: [PATCH 1/3] chore: add experimental d3 track map --- package-lock.json | 708 ++++++++++++++++++ package.json | 1 + src/components/Stats/RaceSummary.tsx | 8 +- src/components/Stats/Slider.tsx | 4 +- src/components/Stats/ThreeBars.tsx | 4 +- src/components/Stats/Tire.tsx | 4 +- src/components/Stats/index.tsx | 6 +- .../{Stats => _experimental}/RankSliderD3.tsx | 0 src/components/_experimental/TrackMap.tsx | 207 +++++ src/libs/react/dimensions.ts | 19 + src/pages/experiments/trackmap.tsx | 19 + .../races/[year]/head-to-head/mclaren/1.tsx | 6 +- 12 files changed, 968 insertions(+), 18 deletions(-) rename src/components/{Stats => _experimental}/RankSliderD3.tsx (100%) create mode 100644 src/components/_experimental/TrackMap.tsx create mode 100644 src/libs/react/dimensions.ts create mode 100644 src/pages/experiments/trackmap.tsx diff --git a/package-lock.json b/package-lock.json index ad7bd46..4c2c4c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@types/node": "^16.11.43", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", + "d3": "^7.6.1", "knex": "^2.2.0", "moment": "^2.29.4", "next": "^12.2.3", @@ -6765,6 +6766,46 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" }, + "node_modules/d3": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz", + "integrity": "sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -6773,16 +6814,186 @@ "internmap": "^1.0.0" } }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" }, + "node_modules/d3-contour": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", + "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour/node_modules/d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" }, + "node_modules/d3-geo": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", + "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-interpolate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", @@ -6796,6 +7007,30 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-scale": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", @@ -6808,6 +7043,26 @@ "d3-time-format": "2 - 3" } }, + "node_modules/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-shape": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", @@ -6832,6 +7087,141 @@ "d3-time": "1 - 2" } }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", + "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -6941,6 +7331,14 @@ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==" }, + "node_modules/delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "dependencies": { + "robust-predicates": "^3.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -15936,6 +16334,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + }, "node_modules/rollup": { "version": "2.76.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.76.0.tgz", @@ -16026,6 +16429,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -23226,6 +23634,112 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" }, + "d3": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz", + "integrity": "sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw==", + "requires": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "dependencies": { + "d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + }, + "d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "requires": { + "d3-color": "1 - 3" + } + }, + "d3-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", + "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==" + }, + "d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "requires": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + } + }, + "d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", + "requires": { + "d3-path": "1 - 3" + } + }, + "d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", + "requires": { + "d3-array": "2 - 3" + } + }, + "d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "requires": { + "d3-time": "1 - 3" + } + } + } + }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -23234,16 +23748,134 @@ "internmap": "^1.0.0" } }, + "d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" + }, + "d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + } + }, + "d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "requires": { + "d3-path": "1 - 3" + } + }, "d3-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" }, + "d3-contour": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", + "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", + "requires": { + "d3-array": "^3.2.0" + }, + "dependencies": { + "d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "requires": { + "internmap": "1 - 2" + } + } + } + }, + "d3-delaunay": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "requires": { + "delaunator": "5" + } + }, + "d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" + }, + "d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + } + }, + "d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "requires": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + } + } + }, + "d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + }, + "d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "requires": { + "d3-dsv": "1 - 3" + } + }, + "d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + } + }, "d3-format": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" }, + "d3-geo": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", + "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", + "requires": { + "d3-array": "2.5.0 - 3" + } + }, + "d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==" + }, "d3-interpolate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", @@ -23257,6 +23889,21 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" }, + "d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==" + }, + "d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" + }, + "d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==" + }, "d3-scale": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", @@ -23269,6 +23916,20 @@ "d3-time-format": "2 - 3" } }, + "d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "requires": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + } + }, + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, "d3-shape": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", @@ -23293,6 +23954,35 @@ "d3-time": "1 - 2" } }, + "d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + } + }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -23373,6 +24063,14 @@ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==" }, + "delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "requires": { + "robust-predicates": "^3.0.0" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -29695,6 +30393,11 @@ "glob": "^7.1.3" } }, + "robust-predicates": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + }, "rollup": { "version": "2.76.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.76.0.tgz", @@ -29755,6 +30458,11 @@ "queue-microtask": "^1.2.2" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", diff --git a/package.json b/package.json index 2dfc7e8..e8fc7f1 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@types/node": "^16.11.43", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", + "d3": "^7.6.1", "knex": "^2.2.0", "moment": "^2.29.4", "next": "^12.2.3", diff --git a/src/components/Stats/RaceSummary.tsx b/src/components/Stats/RaceSummary.tsx index dec5d7b..e459ac3 100644 --- a/src/components/Stats/RaceSummary.tsx +++ b/src/components/Stats/RaceSummary.tsx @@ -11,7 +11,11 @@ interface RaceSummaryProps { }[]; } -const RaceSummary = ({ startingGrid, time, tires }: RaceSummaryProps) => { +export const RaceSummary = ({ + startingGrid, + time, + tires, +}: RaceSummaryProps) => { return (
@@ -33,5 +37,3 @@ const RaceSummary = ({ startingGrid, time, tires }: RaceSummaryProps) => {
); }; - -export default RaceSummary; diff --git a/src/components/Stats/Slider.tsx b/src/components/Stats/Slider.tsx index 89061d0..1960fb1 100644 --- a/src/components/Stats/Slider.tsx +++ b/src/components/Stats/Slider.tsx @@ -8,7 +8,7 @@ interface SliderProps { totalParticipants: number; } -const Slider = (props: SliderProps) => { +export const Slider = (props: SliderProps) => { const rankPercent = ((props.totalParticipants - props.rank) / (props.totalParticipants - 1)) * 100; @@ -62,5 +62,3 @@ const Slider = (props: SliderProps) => {
); }; - -export default Slider; diff --git a/src/components/Stats/ThreeBars.tsx b/src/components/Stats/ThreeBars.tsx index 15ae7d6..f76d816 100644 --- a/src/components/Stats/ThreeBars.tsx +++ b/src/components/Stats/ThreeBars.tsx @@ -15,7 +15,7 @@ interface ThreeBarsProps { leader: Driver; } -const ThreeBars = ({ +export const ThreeBars = ({ title, driver1, driver2, @@ -88,5 +88,3 @@ const ThreeBars = ({ ); }; - -export default ThreeBars; diff --git a/src/components/Stats/Tire.tsx b/src/components/Stats/Tire.tsx index 902461e..619928f 100644 --- a/src/components/Stats/Tire.tsx +++ b/src/components/Stats/Tire.tsx @@ -20,7 +20,7 @@ const compoundMap = { }, }; -const Tire = ({ compound, laps }: TireProps) => { +export const Tire = ({ compound, laps }: TireProps) => { const altSrc = compoundMap[compound]; return ( @@ -32,5 +32,3 @@ const Tire = ({ compound, laps }: TireProps) => { ); }; - -export default Tire; diff --git a/src/components/Stats/index.tsx b/src/components/Stats/index.tsx index 2fa7ebe..8a1b0b7 100644 --- a/src/components/Stats/index.tsx +++ b/src/components/Stats/index.tsx @@ -1,6 +1,6 @@ -import Slider from "./Slider"; -import RaceSummary from "./RaceSummary"; -import ThreeBars from "./ThreeBars"; +import { Slider } from "./Slider"; +import { RaceSummary } from "./RaceSummary"; +import { ThreeBars } from "./ThreeBars"; import { TrackMap } from "./TrackMap"; import { CornerAnalysis } from "./CornerAnalysis"; diff --git a/src/components/Stats/RankSliderD3.tsx b/src/components/_experimental/RankSliderD3.tsx similarity index 100% rename from src/components/Stats/RankSliderD3.tsx rename to src/components/_experimental/RankSliderD3.tsx diff --git a/src/components/_experimental/TrackMap.tsx b/src/components/_experimental/TrackMap.tsx new file mode 100644 index 0000000..79b8d54 --- /dev/null +++ b/src/components/_experimental/TrackMap.tsx @@ -0,0 +1,207 @@ +import * as d3 from "d3"; +import React, { useRef, useEffect, useState, useMemo } from "react"; +import { Dimensions, useRefDimensions } from "libs/react/dimensions"; +import { svg } from "d3"; + +interface DriverData { + driverCode: string; + driverColor: string; + data: Telemetry[]; +} + +type DriverTelemetry = Partial & + TrackMap & { + Code?: string; + }; + +type Telemetry = TrackMap & { + Speed: number; + Distance: number; +}; + +type TrackMap = { + X: number; + Y: number; +}; + +interface TrackMapProps { + color: string; + driverData: DriverData[]; + telemetryOverlay?: "none" | "fastest"; +} + +const pickTelemetryAtDistance = ( + distance: number, + data: Telemetry[] +): Telemetry | undefined => { + for (let i = 1; i < data.length; i++) { + if (data[i].Distance >= distance) { + // TODO: need to interpolate distance...? + return data[i]; + } + } +}; + +const pickFastest = (drivers: DriverData[]): DriverTelemetry[] => { + const tel: DriverTelemetry[] = []; + const fastest = drivers[0].data.forEach((d) => { + const { Speed: speed, Distance: distance } = d; + // TODO: support more than 2 drivers? + const telOtherDriver = pickTelemetryAtDistance(distance, drivers[1].data); + const row: DriverTelemetry = { + X: d.X, + Y: d.Y, + }; + + if (telOtherDriver === undefined) { + tel.push(row); + return; + } + + if (speed > telOtherDriver.Speed) { + tel.push({ + Code: drivers[0].driverCode, + ...d, + }); + } else if (telOtherDriver.Speed > speed) { + tel.push({ + Code: drivers[1].driverCode, + ...telOtherDriver, + ...row, + }); + } else { + tel.push(row); + } + }); + return tel; +}; + +// TODO: maybe use groups? +export const TrackMap = (props: TrackMapProps) => { + const ref = useRef(null); + const dimensions = useRefDimensions(ref); + const svgRef = useRef(null); + const map = props.driverData[0].data; + + // x scale bounds + const xScale = useMemo(() => { + const [xMin, xMax] = d3.extent(map, (m) => { + return m.X; + }); + return d3 + .scaleLinear() + .domain([xMin ?? 0, xMax ?? 0]) + .range([10, dimensions.width - 10]); + }, [map, dimensions.width]); + + // y scale bounds + const yScale = useMemo(() => { + const [yMin, yMax] = d3.extent(map, (m) => m.Y); + return d3 + .scaleLinear() + .domain([yMin ?? 0, yMax ?? 0]) + .range([dimensions.height - 10, 10]); + }, [map, dimensions.height]); + + // draw when scales have changed + useEffect(() => { + if (!xScale || !yScale) { + return; + } + console.log("draw!"); + + d3.select(svgRef.current).selectAll("*").remove(); + drawMap(map); + + if (props.telemetryOverlay === "fastest") { + drawFastestOverlay(props.driverData); + } + }, [props, xScale, yScale]); + + // draw track map + const drawMap = (map: TrackMap[]) => { + const g = d3 + .select(svgRef.current) + .selectAll(".track-map") + .data([map]) + .enter() + .append("g") + .attr("class", "track-map"); + + g.append("path") + .attr( + "d", + d3 + .line() + .x((d) => xScale(d.X)) + .y((d) => yScale(d.Y)) + ) + .attr("fill", "none") + .attr("stroke", props.color) + .attr("shape-rendering", "gerometricPrecision") + .attr("stroke-width", 12) + .exit() + .remove(); + }; + + const drawFastestOverlay = (drivers: DriverData[]) => { + const fastest = pickFastest(drivers); + + drivers.forEach((driver) => { + const data = fastest.map((d) => + d.Code === driver.driverCode ? d : null + ); + + const terminated: DriverTelemetry[][] = [[]]; + let idx = 0; + let prevNull = true; + data.forEach((d) => { + if (d === null) { + if (!prevNull) { + idx++; + } + prevNull = true; + return; + } + if (terminated.length === idx) { + terminated.push([]); + } + prevNull = false; + terminated[idx].push(d); + }); + + const g = d3 + .select(svgRef.current) + .selectAll(`.fastest-overlay-${driver.driverCode}`) + .data(terminated) + .enter() + .append("g") + .attr("class", `fastest-overlay-${driver.driverCode}`); + + g.append("path") + .attr( + "d", + d3 + .line() + .x((d) => xScale(d.X)) + .y((d) => yScale(d.Y)) + ) + .attr("fill", "none") + .attr("stroke", driver.driverColor) + .attr("stroke-width", 8) + .attr("shape-rendering", "geometricPrecision") + .exit() + .remove(); + }); + }; + + return ( +
+ +
+ ); +}; + +TrackMap.defaultProps = { + telemetryOverlay: "none", +}; diff --git a/src/libs/react/dimensions.ts b/src/libs/react/dimensions.ts new file mode 100644 index 0000000..42590a8 --- /dev/null +++ b/src/libs/react/dimensions.ts @@ -0,0 +1,19 @@ +import React, { useState } from "react"; + +export interface Dimensions { + width: number; + height: number; +} + +export const useRefDimensions = (ref: React.RefObject): Dimensions => { + const [dimensions, setDimensions] = useState({ width: 1, height: 2 }); + React.useEffect(() => { + if (ref.current) { + const { current } = ref; + const boundingRect = current.getBoundingClientRect(); + const { width, height } = boundingRect; + setDimensions({ width: Math.round(width), height: Math.round(height) }); + } + }, [ref]); + return dimensions; +}; diff --git a/src/pages/experiments/trackmap.tsx b/src/pages/experiments/trackmap.tsx new file mode 100644 index 0000000..41e4daf --- /dev/null +++ b/src/pages/experiments/trackmap.tsx @@ -0,0 +1,19 @@ +import { TrackMap } from "components/_experimental/TrackMap"; +import { NorrisData, RicciardoData } from "libs/data/mclaren"; + +export default function TrackMapExperiment() { + return ( +
+
+ +
+
+ ); +} diff --git a/src/pages/races/[year]/head-to-head/mclaren/1.tsx b/src/pages/races/[year]/head-to-head/mclaren/1.tsx index 0a27859..4871d33 100644 --- a/src/pages/races/[year]/head-to-head/mclaren/1.tsx +++ b/src/pages/races/[year]/head-to-head/mclaren/1.tsx @@ -68,7 +68,7 @@ export default function MclarenHeadToHead(props: HeadToHeadProps) {
Date: Tue, 9 Aug 2022 17:59:18 -0400 Subject: [PATCH 2/3] chore: add map legends --- src/components/_experimental/TrackMap.tsx | 43 ++++++++++++++++------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/components/_experimental/TrackMap.tsx b/src/components/_experimental/TrackMap.tsx index 79b8d54..8a2f074 100644 --- a/src/components/_experimental/TrackMap.tsx +++ b/src/components/_experimental/TrackMap.tsx @@ -24,12 +24,6 @@ type TrackMap = { Y: number; }; -interface TrackMapProps { - color: string; - driverData: DriverData[]; - telemetryOverlay?: "none" | "fastest"; -} - const pickTelemetryAtDistance = ( distance: number, data: Telemetry[] @@ -76,13 +70,20 @@ const pickFastest = (drivers: DriverData[]): DriverTelemetry[] => { return tel; }; -// TODO: maybe use groups? +interface TrackMapProps { + color: string; + driverData: DriverData[]; + telemetryOverlay?: "none" | "fastest"; +} + export const TrackMap = (props: TrackMapProps) => { const ref = useRef(null); const dimensions = useRefDimensions(ref); const svgRef = useRef(null); const map = props.driverData[0].data; + const [mx, my] = [14, 14]; + // x scale bounds const xScale = useMemo(() => { const [xMin, xMax] = d3.extent(map, (m) => { @@ -91,7 +92,7 @@ export const TrackMap = (props: TrackMapProps) => { return d3 .scaleLinear() .domain([xMin ?? 0, xMax ?? 0]) - .range([10, dimensions.width - 10]); + .range([mx, dimensions.width - mx]); }, [map, dimensions.width]); // y scale bounds @@ -100,7 +101,7 @@ export const TrackMap = (props: TrackMapProps) => { return d3 .scaleLinear() .domain([yMin ?? 0, yMax ?? 0]) - .range([dimensions.height - 10, 10]); + .range([dimensions.height - my, my]); }, [map, dimensions.height]); // draw when scales have changed @@ -147,7 +148,7 @@ export const TrackMap = (props: TrackMapProps) => { const drawFastestOverlay = (drivers: DriverData[]) => { const fastest = pickFastest(drivers); - drivers.forEach((driver) => { + drivers.forEach((driver, driverIdx) => { const data = fastest.map((d) => d.Code === driver.driverCode ? d : null ); @@ -189,9 +190,27 @@ export const TrackMap = (props: TrackMapProps) => { .attr("fill", "none") .attr("stroke", driver.driverColor) .attr("stroke-width", 8) + .attr("shape-rendering", "geometricPrecision"); + + const legend = d3.select(svgRef.current); + const margin = 22; + const radius = 6; + const x = mx; + const y = dimensions.height - my - driverIdx * margin; + legend + .append("circle") + .attr("cx", x) + .attr("cy", y - 5) + .attr("r", radius) .attr("shape-rendering", "geometricPrecision") - .exit() - .remove(); + .style("fill", driver.driverColor); + legend + .append("text") + .attr("x", x + radius + 10) + .attr("y", y) + .style("fill", driver.driverColor) + .text(`${driver.driverCode} is faster`) + .style("alignment-baseline", "middle"); }); }; From 43f233aae79aa4f22e71ad64d2f822bd0db893d6 Mon Sep 17 00:00:00 2001 From: David Harrigan Date: Thu, 11 Aug 2022 21:47:30 -0400 Subject: [PATCH 3/3] wip: get some lines goin --- .../_experimental/CornerAnalysis.tsx | 99 +++++++++++++++++++ src/components/_experimental/TrackMap.tsx | 34 ++----- src/libs/types.ts | 17 ++++ src/pages/experiments/trackmap.tsx | 14 ++- 4 files changed, 138 insertions(+), 26 deletions(-) create mode 100644 src/components/_experimental/CornerAnalysis.tsx create mode 100644 src/libs/types.ts diff --git a/src/components/_experimental/CornerAnalysis.tsx b/src/components/_experimental/CornerAnalysis.tsx new file mode 100644 index 0000000..b5f31c1 --- /dev/null +++ b/src/components/_experimental/CornerAnalysis.tsx @@ -0,0 +1,99 @@ +import * as d3 from "d3"; +import React, { useRef, useEffect, useMemo } from "react"; +import { useRefDimensions } from "libs/react/dimensions"; +import type { DriverData, Telemetry } from "libs/types"; + +interface CornerAnalysisProps { + turnNumber: number; + driverData: DriverData[]; + distanceFrom: number; + distanceTo: number; +} + +export const CornerAnalysis = (props: CornerAnalysisProps) => { + const ref = useRef(null); + const dimensions = useRefDimensions(ref); + const svgRef = useRef(null); + const allDriverData: DriverTelemetry[] = props.driverData + .map((d) => { + return d.data.map((tel) => { + return { + ...tel, + Code: d.driverCode, + Color: d.driverColor, + }; + }); + }) + .flat(); + + const [mx, my] = [14, 14]; + + // x scale bounds + const distanceScale = useMemo(() => { + const [xMin, xMax] = [props.distanceFrom, props.distanceTo]; + return d3 + .scaleLinear() + .domain([xMin ?? 0, xMax ?? 0]) + .range([mx, dimensions.width - mx]); + }, [allDriverData, dimensions.width, mx]); + + // y scale bounds + const speedScale = useMemo(() => { + const [yMin, yMax] = d3.extent(allDriverData, (d) => d.Speed); + return d3 + .scaleLinear() + .domain([yMin ?? 0, yMax ?? 0]) + .range([dimensions.height - my, my]); + }, [allDriverData, dimensions.height, my]); + + // draw + useEffect(() => { + if (!distanceScale || !speedScale) { + return; + } + + d3.select(svgRef.current).selectAll("*").remove(); + drawSpeedGraph(allDriverData); + }, [props, distanceScale, speedScale]); + + const drawSpeedGraph = () => { + const color = d3 + .scaleOrdinal() + .domain(props.driverData.map((d) => d.driverCode)) + .range(props.driverData.map((d) => d.driverColor)); + + const g = d3 + .select(svgRef.current) + .selectAll(`.speed-graph`) + .data(props.driverData) + .enter() + .append("g") + .attr("class", "speed-graph"); + + g.append("path") + .attr("d", (d) => { + return d3 + .line() + .x((d) => { + return distanceScale(d.Distance); + }) + .y((d) => speedScale(d.Speed))(d.data); + }) + .attr("fill", "none") + .attr("stroke-width", 2) + .attr("stroke", (d) => color(d.driverCode)); + }; + + return ( +
+
+

+ Corner Analysis - Turn {props.turnNumber} 🏎️ 🏎️ 🏎️ +

+
+
+ +
+
+ ); +}; diff --git a/src/components/_experimental/TrackMap.tsx b/src/components/_experimental/TrackMap.tsx index 8a2f074..177efb5 100644 --- a/src/components/_experimental/TrackMap.tsx +++ b/src/components/_experimental/TrackMap.tsx @@ -1,25 +1,10 @@ import * as d3 from "d3"; import React, { useRef, useEffect, useState, useMemo } from "react"; -import { Dimensions, useRefDimensions } from "libs/react/dimensions"; -import { svg } from "d3"; +import { useRefDimensions } from "libs/react/dimensions"; +import type { TrackData, DriverData, Telemetry } from "libs/types"; -interface DriverData { - driverCode: string; - driverColor: string; - data: Telemetry[]; -} - -type DriverTelemetry = Partial & - TrackMap & { - Code?: string; - }; - -type Telemetry = TrackMap & { - Speed: number; - Distance: number; -}; - -type TrackMap = { +type DriverTelemetry = Partial & { + Code?: string; X: number; Y: number; }; @@ -93,7 +78,7 @@ export const TrackMap = (props: TrackMapProps) => { .scaleLinear() .domain([xMin ?? 0, xMax ?? 0]) .range([mx, dimensions.width - mx]); - }, [map, dimensions.width]); + }, [map, dimensions.width, mx]); // y scale bounds const yScale = useMemo(() => { @@ -102,14 +87,13 @@ export const TrackMap = (props: TrackMapProps) => { .scaleLinear() .domain([yMin ?? 0, yMax ?? 0]) .range([dimensions.height - my, my]); - }, [map, dimensions.height]); + }, [map, dimensions.height, my]); // draw when scales have changed useEffect(() => { if (!xScale || !yScale) { return; } - console.log("draw!"); d3.select(svgRef.current).selectAll("*").remove(); drawMap(map); @@ -120,11 +104,11 @@ export const TrackMap = (props: TrackMapProps) => { }, [props, xScale, yScale]); // draw track map - const drawMap = (map: TrackMap[]) => { + const drawMap = (map: TrackData[]) => { const g = d3 .select(svgRef.current) .selectAll(".track-map") - .data([map]) + .data([map]) .enter() .append("g") .attr("class", "track-map"); @@ -133,7 +117,7 @@ export const TrackMap = (props: TrackMapProps) => { .attr( "d", d3 - .line() + .line() .x((d) => xScale(d.X)) .y((d) => yScale(d.Y)) ) diff --git a/src/libs/types.ts b/src/libs/types.ts new file mode 100644 index 0000000..38e5aad --- /dev/null +++ b/src/libs/types.ts @@ -0,0 +1,17 @@ +export interface DriverData { + driverCode: string; + driverColor: string; + data: Telemetry[]; +} + +export type Telemetry = TrackData & { + Speed: number; + Distance: number; + Brake: boolean; + Throttle: number; +}; + +export type TrackData = { + X: number; + Y: number; +}; diff --git a/src/pages/experiments/trackmap.tsx b/src/pages/experiments/trackmap.tsx index 41e4daf..10ccfcf 100644 --- a/src/pages/experiments/trackmap.tsx +++ b/src/pages/experiments/trackmap.tsx @@ -1,9 +1,10 @@ import { TrackMap } from "components/_experimental/TrackMap"; +import { CornerAnalysis } from "components/_experimental/CornerAnalysis"; import { NorrisData, RicciardoData } from "libs/data/mclaren"; export default function TrackMapExperiment() { return ( -
+
+
+ +
); }