From e1c6244af4cf6f8cbe28db98a4c1cd98cc08cbdb Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Thu, 2 Nov 2023 19:10:23 -0700 Subject: [PATCH 01/24] removing center column and refactoring toggle feature --- src/App.css | 9 ++++++--- src/App.js | 30 +++++++++++++++++------------- src/telemetry_data.json | 2 +- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/App.css b/src/App.css index 7d82568..0edabf1 100644 --- a/src/App.css +++ b/src/App.css @@ -53,9 +53,12 @@ /* ----------------------------------- */ #fireSimButton { - margin-top: 7px; + margin-top: 20px; width: 150px; height: 50px; + margin-left: auto; + margin-right: auto; + margin-bottom: 20px; } #simArgSection { @@ -101,11 +104,11 @@ font-size: 20px; } -.toggleStats { +#toggleStats { color: white; margin: auto; border: 1px solid grey; - + margin-top: 20px; } .selectDiv { diff --git a/src/App.js b/src/App.js index 0ab6e96..b076c6b 100644 --- a/src/App.js +++ b/src/App.js @@ -170,7 +170,7 @@ class App extends Component { value: this.state.rightCol, onChange: (e) => { if(this.state.rightCol === 'stats') { - this.setState({rightCol:'bot'}) + this.setState({rightCol:'map'}) } else { this.setState({rightCol: 'stats'}) } @@ -189,7 +189,7 @@ class App extends Component { /> ); } else { - return(); + return( ); } } return ( @@ -197,14 +197,18 @@ class App extends Component { - -
Graphs
-
Bot
-
- {statProvider()} - - - + + + + + + +
Graphs
+
Map
+
+ +
+ - - - + + {statProvider()}
diff --git a/src/telemetry_data.json b/src/telemetry_data.json index ba58f26..029aa91 100644 --- a/src/telemetry_data.json +++ b/src/telemetry_data.json @@ -1 +1 @@ -"{\n \"_time\": \"2023-09-30T22:32:50Z\",\n \"bad_hall_sequence\": 0.0,\n \"bridge_pwm_flag\": 0.0,\n \"bus_current\": 0.0,\n \"bus_current_flag\": 0.0,\n \"bus_voltage\": 105.158732096354,\n \"bus_voltage_lower_limit_flag\": 0.0,\n \"bus_voltage_upper_limit_flag\": 0.0,\n \"config_read_error\": 0.0,\n \"current_setpoint\": 0.0,\n \"dc_bus_overvoltage\": 0.0,\n \"desired_velocity\": 0.0,\n \"heat_sink_temp_flag\": 0.0,\n \"heatsink_temperature\": 23.0060119628906,\n \"hw_overcurrent\": 0.0,\n \"motor_current_flag\": 0.0,\n \"motor_temperature\": 29.7648010253906,\n \"motor_velocity\": 0.0,\n \"phase_a_current\": 0.246654238019671,\n \"phase_b_current\": 0.306003434317453,\n \"sw_overcurrent\": 0.0,\n \"undervoltage_lockout\": 0.0,\n \"vehicle_velocity\": 0.0,\n \"velocity_flag\": 0.0,\n \"watchdog_reset\": 0.0\n}" \ No newline at end of file +"{}" \ No newline at end of file From d4d66b7ad9f6f22d90740ad0d530e72080c5e083 Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Thu, 19 Oct 2023 19:51:40 -0700 Subject: [PATCH 02/24] added sending sim args json to back end through port message --- scripts/sim.py | 2 +- src/App.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/sim.py b/scripts/sim.py index ee3a991..96d7d57 100644 --- a/scripts/sim.py +++ b/scripts/sim.py @@ -72,7 +72,7 @@ def run_sim_once(): command = input() # print(f"(Python Script): Received the following input from hidden renderer: {command}") - if command == 'run_sim': + if command.split(' ')[0] == 'run_sim': # expected: command = "run_sim" + " " + JSON String of SimArgs from front-end # TODO: May want to create a thread run_sim_once() print("simulation_run_complete") diff --git a/src/App.js b/src/App.js index b076c6b..37c84ce 100644 --- a/src/App.js +++ b/src/App.js @@ -105,7 +105,8 @@ class App extends Component { startSim = () => { console.log("RE-RUNNING SIMULATION") - window.port.postMessage('run_sim') + console.log(this.state.simArgs) + window.port.postMessage('run_sim' + ' ' + JSON.stringify(this.state.simArgs)) // send sim args as a json string as a part of the command this.setState({ loading: true, displayMap: false, From a0ecbaa08bc1026f0c884e459bb9d888758cf759 Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Thu, 26 Oct 2023 18:30:08 -0700 Subject: [PATCH 03/24] updated styling for middle col, added graph hover --- src/App.css | 19 +++++++++++++++---- src/App.js | 22 +++++++++------------- src/Subcomponents/Stats.js | 6 +++--- src/Subcomponents/ValueTable.js | 2 +- src/Subcomponents/ZoomChart.js | 20 ++++++++++++++++++-- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/App.css b/src/App.css index 0edabf1..e3727d7 100644 --- a/src/App.css +++ b/src/App.css @@ -38,8 +38,12 @@ #centerRow { /* background-color: white; */ - padding-right: 25px; + /* padding-right: 25px; */ padding-top: 2px; + display: flex; + flex-direction: column; + align-items: center; + row-gap: 2vh; } #rightRow { @@ -48,6 +52,8 @@ height: 100vh; } + + /* SIMULATION ARGUMENTS SECTION */ /* ----------------------------------- */ /* ----------------------------------- */ @@ -62,7 +68,7 @@ } #simArgSection { - margin-top: 20px; + /* margin-top: 20px; */ } .simArgDiv { @@ -115,6 +121,12 @@ } +.graph_card { + display: flex; + justify-content: center; + align-items: center; +} + /* Zoom Chart Styling */ /* ----------------------------------- */ @@ -134,6 +146,7 @@ .plot-container { /* position: relative; */ + /* padding: 20px; */ } .chartButton { @@ -142,8 +155,6 @@ } - - /* Map styling */ /* ----------------------------------- */ /* ----------------------------------- */ diff --git a/src/App.js b/src/App.js index 37c84ce..9745dec 100644 --- a/src/App.js +++ b/src/App.js @@ -198,18 +198,14 @@ class App extends Component { - - - - - - -
Graphs
-
Map
-
- -
- + +
Graphs
+
Bot
+
+ {statProvider()} + + + - {"distance traveled: " + Math.round(this.props.json["distance_travelled"]) +" km"}
  • {"time taken: "}
    {secondsToDhms(this.props.json["time_taken"])}
  • {"final SOC: " + Math.round(this.props.json["final_soc"])}
  • -
  • {this.createGraph("speed_kmh")}
  • -
  • {this.createGraph("distances")}
  • -
  • {this.createGraph("state_of_charge")}
  • +
  • {this.createGraph("speed_kmh")}
  • +
  • {this.createGraph("distances")}
  • +
  • {this.createGraph("state_of_charge")}
  • {/*
  • {this.createGraph("influx_soc")}
  • */} diff --git a/src/Subcomponents/ValueTable.js b/src/Subcomponents/ValueTable.js index ca20fa7..d1ed651 100644 --- a/src/Subcomponents/ValueTable.js +++ b/src/Subcomponents/ValueTable.js @@ -27,7 +27,7 @@ class ValueTable extends Component { {Object.keys(currentValues).map(key => ( {key} - {currentValues[key]} + {Math.round(currentValues[key] * 1000) / 1000} {/* Round to 4 decimal places */} {expectedValues[key]} ))} diff --git a/src/Subcomponents/ZoomChart.js b/src/Subcomponents/ZoomChart.js index ecf4121..157113c 100644 --- a/src/Subcomponents/ZoomChart.js +++ b/src/Subcomponents/ZoomChart.js @@ -1,13 +1,13 @@ import React, { useState } from "react"; -import { LineChart, Line, ReferenceArea, XAxis, YAxis } from "recharts"; +import { LineChart, Line, ReferenceArea, XAxis, YAxis, Tooltip } from "recharts"; import '../App.css'; const MIN_ZOOM = 5; // adjust based on your data const DEFAULT_ZOOM = { x1: null, y1: null, x2: null, y2: null }; -const CHART_WIDTH = 400; +const CHART_WIDTH = 380; const Normalize = (min, max, dataset) => { @@ -96,6 +96,18 @@ export default function ZoomChart(props) { } } + const CustomTooltip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + return ( +
    +

    {`(${Math.round(label * 100) / 100} , ${Math.round(payload[0].value * 100) / 100})`}

    {/* (x, y) pair rounded to 2 decimal places */} +
    + ); + } + + return null; + }; + return (
    {props.name}
    @@ -112,6 +124,7 @@ export default function ZoomChart(props) { type="number" dataKey="x" domain={["auto", "auto"]} + tick={{fontSize: 13}} stroke="white" /> + +
    ); From f4529703d41835591b7a28b216f3486dc22adbdf Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Thu, 26 Oct 2023 18:31:12 -0700 Subject: [PATCH 04/24] packages --- package-lock.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/package-lock.json b/package-lock.json index fe338f8..7b85b43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4200,6 +4200,25 @@ "node": ">=10" } }, + "node_modules/@testing-library/dom": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", + "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@testing-library/jest-dom": { "version": "5.16.5", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", @@ -15932,6 +15951,19 @@ "node": ">= 8" } }, + "node_modules/react-scripts/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "optional": true, + "peer": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-scripts/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", From bda6c3fd0cb330911e28d6e0cb0e1b3b7fe24f8e Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Thu, 26 Oct 2023 18:32:12 -0700 Subject: [PATCH 05/24] css file fix --- src/App.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.css b/src/App.css index e3727d7..5a80fa3 100644 --- a/src/App.css +++ b/src/App.css @@ -17,7 +17,7 @@ } .container { - max-width: '100%' //This will make container to take screen width + max-width: '100%'; /* This will make container to take screen width */ } From f05f6c72232f33823044b3074462fdf2c6921c83 Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Thu, 2 Nov 2023 18:34:00 -0700 Subject: [PATCH 06/24] Quering Telemetry Data. Dumping data to telemetry_data.json --- src/telemetry_data.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/telemetry_data.json b/src/telemetry_data.json index 029aa91..e1f1280 100644 --- a/src/telemetry_data.json +++ b/src/telemetry_data.json @@ -1 +1,5 @@ -"{}" \ No newline at end of file +<<<<<<< HEAD +"{}" +======= +"{\n \"_time\": \"2023-09-30T22:32:50Z\",\n \"bad_hall_sequence\": 0.0,\n \"bridge_pwm_flag\": 0.0,\n \"bus_current\": 0.0,\n \"bus_current_flag\": 0.0,\n \"bus_voltage\": 105.158732096354,\n \"bus_voltage_lower_limit_flag\": 0.0,\n \"bus_voltage_upper_limit_flag\": 0.0,\n \"config_read_error\": 0.0,\n \"current_setpoint\": 0.0,\n \"dc_bus_overvoltage\": 0.0,\n \"desired_velocity\": 0.0,\n \"heat_sink_temp_flag\": 0.0,\n \"heatsink_temperature\": 23.0060119628906,\n \"hw_overcurrent\": 0.0,\n \"motor_current_flag\": 0.0,\n \"motor_temperature\": 29.7648010253906,\n \"motor_velocity\": 0.0,\n \"phase_a_current\": 0.246654238019671,\n \"phase_b_current\": 0.306003434317453,\n \"sw_overcurrent\": 0.0,\n \"undervoltage_lockout\": 0.0,\n \"vehicle_velocity\": 0.0,\n \"velocity_flag\": 0.0,\n \"watchdog_reset\": 0.0\n}" +>>>>>>> b3478c6 (Quering Telemetry Data. Dumping data to telemetry_data.json) From 572f7a2cc4ee486282e529e209d36b32a3052a58 Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Thu, 2 Nov 2023 19:10:23 -0700 Subject: [PATCH 07/24] removing center column and refactoring toggle feature --- src/App.js | 22 +++++++++++++--------- src/telemetry_data.json | 4 ---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/App.js b/src/App.js index 9745dec..37c84ce 100644 --- a/src/App.js +++ b/src/App.js @@ -198,14 +198,18 @@ class App extends Component { - -
    Graphs
    -
    Bot
    -
    - {statProvider()} - - - + + + + + + +
    Graphs
    +
    Map
    +
    + +
    + - >>>>>> b3478c6 (Quering Telemetry Data. Dumping data to telemetry_data.json) From 304c9b470682330a2ec9e665bdec0e4a206c9a1a Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 4 Nov 2023 13:10:32 -0700 Subject: [PATCH 08/24] switching to D3.js for graphing. This library is more expansive and supports more control over graph settings --- package-lock.json | 332 ++++++++++++++++++++++++++++++++----- package.json | 1 + src/App.css | 16 +- src/Subcomponents/Graph.js | 69 ++++++++ src/Subcomponents/Stats.js | 13 +- src/index.js | 4 +- src/telemetry_data.json | 2 +- 7 files changed, 370 insertions(+), 67 deletions(-) create mode 100644 src/Subcomponents/Graph.js diff --git a/package-lock.json b/package-lock.json index 7b85b43..9061d52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "bootstrap": "^5.2.3", + "d3": "^7.8.5", "leaflet": "^1.9.4", "openai": "^3.3.0", "python-shell": "^5.0.0", @@ -4200,25 +4201,6 @@ "node": ">=10" } }, - "node_modules/@testing-library/dom": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", - "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@testing-library/jest-dom": { "version": "5.16.5", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", @@ -7226,6 +7208,46 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "node_modules/d3": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "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": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -7237,6 +7259,40 @@ "node": ">=12" } }, + "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": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -7245,6 +7301,80 @@ "node": ">=12" } }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "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", @@ -7253,6 +7383,30 @@ "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": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -7261,6 +7415,25 @@ "node": ">=12" } }, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "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": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", @@ -7280,6 +7453,30 @@ "node": ">=12" } }, + "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": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -7295,6 +7492,26 @@ "node": ">=12" } }, + "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": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", @@ -7336,6 +7553,39 @@ "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/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -7512,6 +7762,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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", @@ -15951,19 +16209,6 @@ "node": ">= 8" } }, - "node_modules/react-scripts/node_modules/type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", - "optional": true, - "peer": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/react-scripts/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -16421,6 +16666,11 @@ "dev": true, "optional": true }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/rollup": { "version": "2.79.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", @@ -16493,6 +16743,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/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -17951,19 +18206,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index 866406a..2e8889a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "bootstrap": "^5.2.3", + "d3": "^7.8.5", "leaflet": "^1.9.4", "openai": "^3.3.0", "python-shell": "^5.0.0", diff --git a/src/App.css b/src/App.css index 5a80fa3..3523f27 100644 --- a/src/App.css +++ b/src/App.css @@ -86,25 +86,23 @@ /* ----------------------------------- */ /* ----------------------------------- */ -.statUL { - list-style-type: none; +#statDiv { padding: 20px; margin: auto; - width: 450px; + width: 85%; + height: 90vh; + } -.statUL li { +#graphBox { outline-style: solid; margin: 10px; - margin-left: 0px; - padding: 15px; - padding-left: 0px; + padding: 20px; font-size: 15px; background-color: #022D36; - + height: 100%; } - .graphTitle { font-weight: bold; font-size: 20px; diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js new file mode 100644 index 0000000..a5cd7a5 --- /dev/null +++ b/src/Subcomponents/Graph.js @@ -0,0 +1,69 @@ +import * as d3 from "d3"; +import React, {useEffect, useRef, useMemo, useState} from "react"; + +export default function Graph(props) { + const createGraph = async () => { + console.log("testing"); + console.log(props.data) + let values1 = props.data['speed_kmh']; + let tick = 0; + let data = []; + values1.forEach((d) => { + const dataPoint = { + tick: tick++, + value: d, + } + data.push(dataPoint); + }); + let values2 = props.data['state_of_charge']; + console.log(data) + + // set the dimensions and margins of the graph + var margin = { top: 20, right: 50, bottom: 50, left: 70 }; + + // Get the width of the "graphBox" div + var graphBox = document.getElementById('graphBox'); + var width = graphBox.clientWidth - margin.left - margin.right; + var height = graphBox.clientHeight - margin.top - margin.bottom; + + // append the svg object to the body of the page + var svg = d3.select("#graphBox").append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", `translate(${margin.left}, ${margin.top})`); + + // Add X axis and Y axis + var x1 = d3.scaleTime().range([0, width]); + var y1 = d3.scaleLinear().range([height, 0]); + x1.domain(d3.extent(data, (d) => { return d.tick; })); + y1.domain([0, d3.max(data, (d) => { return d.value; })]); + svg.append("g") + .attr("transform", `translate(0, ${height})`) + .call(d3.axisBottom(x1)); + svg.append("g") + .call(d3.axisLeft(y1)); + + // add the Line + var valueLine = d3.line() + .x((d) => { return x1(d.tick); }) + .y((d) => { return y1(d.value); }); + svg.append("path") + .data([data]) + .attr("class", "line") + .attr("fill", "none") + .attr("stroke", "steelblue") + .attr("stroke-width", 1.5) + .attr("d", valueLine) + } + + useEffect(() => { + createGraph(); + return () => { + // Cleanup code to remove the SVG element + d3.select('#graphBox svg').remove(); + }; + }, []); + + return(<>); +} \ No newline at end of file diff --git a/src/Subcomponents/Stats.js b/src/Subcomponents/Stats.js index c39ca2c..be7fb62 100644 --- a/src/Subcomponents/Stats.js +++ b/src/Subcomponents/Stats.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import ZoomChart from './ZoomChart' +import Graph from './Graph' import secondsToDhms from "../HelperFunctions/TimeString" import '../App.css'; @@ -22,15 +23,9 @@ class Stats extends Component { if (this.props.json["empty"] === undefined){ return(
    -
      -
    • {"distance traveled: " + Math.round(this.props.json["distance_travelled"]) +" km"}
    • -
    • {"time taken: "}
      {secondsToDhms(this.props.json["time_taken"])}
    • -
    • {"final SOC: " + Math.round(this.props.json["final_soc"])}
    • -
    • {this.createGraph("speed_kmh")}
    • -
    • {this.createGraph("distances")}
    • -
    • {this.createGraph("state_of_charge")}
    • - {/*
    • {this.createGraph("influx_soc")}
    • */} -
    +
    + +
    ); diff --git a/src/index.js b/src/index.js index d563c0f..7e22409 100644 --- a/src/index.js +++ b/src/index.js @@ -6,9 +6,7 @@ import reportWebVitals from './reportWebVitals'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - - + ); // If you want to start measuring performance in your app, pass a function diff --git a/src/telemetry_data.json b/src/telemetry_data.json index 63084fb..029aa91 100644 --- a/src/telemetry_data.json +++ b/src/telemetry_data.json @@ -1 +1 @@ -"{\n \"_time\": \"2023-09-30T22:32:50Z\",\n \"bad_hall_sequence\": 0.0,\n \"bridge_pwm_flag\": 0.0,\n \"bus_current\": 0.0,\n \"bus_current_flag\": 0.0,\n \"bus_voltage\": 105.158732096354,\n \"bus_voltage_lower_limit_flag\": 0.0,\n \"bus_voltage_upper_limit_flag\": 0.0,\n \"config_read_error\": 0.0,\n \"current_setpoint\": 0.0,\n \"dc_bus_overvoltage\": 0.0,\n \"desired_velocity\": 0.0,\n \"heat_sink_temp_flag\": 0.0,\n \"heatsink_temperature\": 23.0060119628906,\n \"hw_overcurrent\": 0.0,\n \"motor_current_flag\": 0.0,\n \"motor_temperature\": 29.7648010253906,\n \"motor_velocity\": 0.0,\n \"phase_a_current\": 0.246654238019671,\n \"phase_b_current\": 0.306003434317453,\n \"sw_overcurrent\": 0.0,\n \"undervoltage_lockout\": 0.0,\n \"vehicle_velocity\": 0.0,\n \"velocity_flag\": 0.0,\n \"watchdog_reset\": 0.0\n}" +"{}" \ No newline at end of file From 67ab24122a8d929ba53f939b6433f59e1fda1d1a Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 4 Nov 2023 13:33:52 -0700 Subject: [PATCH 09/24] adding multiple y-axis --- src/Subcomponents/Graph.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index a5cd7a5..bb9f41c 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -11,11 +11,14 @@ export default function Graph(props) { values1.forEach((d) => { const dataPoint = { tick: tick++, - value: d, + speed: d, } data.push(dataPoint); }); let values2 = props.data['state_of_charge']; + values2.forEach((p, index) => { + data[index].soc = p; + }) console.log(data) // set the dimensions and margins of the graph @@ -34,20 +37,30 @@ export default function Graph(props) { .attr("transform", `translate(${margin.left}, ${margin.top})`); // Add X axis and Y axis - var x1 = d3.scaleTime().range([0, width]); + var x1 = d3.scaleLinear().range([0, width]); var y1 = d3.scaleLinear().range([height, 0]); x1.domain(d3.extent(data, (d) => { return d.tick; })); - y1.domain([0, d3.max(data, (d) => { return d.value; })]); + y1.domain([0, d3.max(data, (d) => { return d.speed; })]); svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x1)); svg.append("g") .call(d3.axisLeft(y1)); + + var y2 = d3.scaleLinear().range([height, 0]); + y2.domain([0, d3.max(data, (d) => { return d.soc; })]); + // Append the y2-axis to the far left and shift it over some distance + let YAxis2 = svg.append("g") + .attr("transform", `translate(-30, 0)`) // Move to the left and shift it by 20 units + .call(d3.axisLeft(y2)); + + YAxis2.selectAll("text") + .style("fill", "#fcba03"); // add the Line var valueLine = d3.line() .x((d) => { return x1(d.tick); }) - .y((d) => { return y1(d.value); }); + .y((d) => { return y1(d.speed); }); svg.append("path") .data([data]) .attr("class", "line") From dd6915fe57e228a16ebfa253f28cd65b4274c9de Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 4 Nov 2023 14:37:16 -0700 Subject: [PATCH 10/24] displaying multiple graphs dynamically chosen with multi-select --- src/App.js | 2 +- src/Subcomponents/Graph.js | 106 ++++++++++++++++--------------- src/Subcomponents/MultiSelect.js | 7 +- src/Subcomponents/Stats.js | 2 +- 4 files changed, 60 insertions(+), 57 deletions(-) diff --git a/src/App.js b/src/App.js index 37c84ce..9aaaafa 100644 --- a/src/App.js +++ b/src/App.js @@ -162,7 +162,6 @@ class App extends Component { this.setState({ ExtraGraphs: typeof value === 'string' ? value.split(',') : value, }); - console.log(typeof value === 'string' ? value.split(',') : value,); } @@ -187,6 +186,7 @@ class App extends Component { json={this.state.json} handleChange={this.handleChangeSelect} Select={this.state.ExtraGraphs} + ExtraGraphs={this.state.ExtraGraphs} /> ); } else { diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index bb9f41c..9d6b8c8 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -2,72 +2,74 @@ import * as d3 from "d3"; import React, {useEffect, useRef, useMemo, useState} from "react"; export default function Graph(props) { + const [graphList, setGraphList] = useState(props.graphs); + const createGraph = async () => { - console.log("testing"); - console.log(props.data) - let values1 = props.data['speed_kmh']; - let tick = 0; - let data = []; - values1.forEach((d) => { - const dataPoint = { - tick: tick++, - speed: d, - } - data.push(dataPoint); + // data array will store datapoints for the selected graphs + let data = []; + let colors = ['#fcba03', "#6b03fc", "#cc0808", "#3bc708"] + // initializing data array to have the correct size and a tick value at each point. + // TODO: Data tick values are not the corresponding simulation output ticks since simulation outputs have been shortened. + props.data['speed_kmh'].forEach((value, tick) => { + data[tick] = {tick: tick} + }) + + graphList.forEach((dataName) => { + let values1 = props.data[dataName]; + values1.forEach((d, index) => { + data[index][dataName] = d; }); - let values2 = props.data['state_of_charge']; - values2.forEach((p, index) => { - data[index].soc = p; - }) - console.log(data) - - // set the dimensions and margins of the graph - var margin = { top: 20, right: 50, bottom: 50, left: 70 }; + }); + - // Get the width of the "graphBox" div - var graphBox = document.getElementById('graphBox'); - var width = graphBox.clientWidth - margin.left - margin.right; - var height = graphBox.clientHeight - margin.top - margin.bottom; + var leftMargin = graphList.length * 37.5; + // set the dimensions and margins of the graph + var margin = { top: 20, right: 50, bottom: 50, left: leftMargin }; - // append the svg object to the body of the page - var svg = d3.select("#graphBox").append("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", `translate(${margin.left}, ${margin.top})`); + // Get the width of the "graphBox" div + var graphBox = document.getElementById('graphBox'); + var width = graphBox.clientWidth - margin.left - margin.right; + var height = graphBox.clientHeight - margin.top - margin.bottom; - // Add X axis and Y axis - var x1 = d3.scaleLinear().range([0, width]); - var y1 = d3.scaleLinear().range([height, 0]); - x1.domain(d3.extent(data, (d) => { return d.tick; })); - y1.domain([0, d3.max(data, (d) => { return d.speed; })]); - svg.append("g") - .attr("transform", `translate(0, ${height})`) - .call(d3.axisBottom(x1)); - svg.append("g") - .call(d3.axisLeft(y1)); - - var y2 = d3.scaleLinear().range([height, 0]); - y2.domain([0, d3.max(data, (d) => { return d.soc; })]); + // append the svg object to the body of the page + var svg = d3.select("#graphBox").append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", `translate(${margin.left}, ${margin.top})`); + + + var x = d3.scaleLinear().range([0, width]); + x.domain(d3.extent(data, (d) => { return d.tick; })); + svg.append("g") + .attr("transform", `translate(0, ${height})`) + .call(d3.axisBottom(x)); + + let graphNumber = 0; + graphList.forEach((dataName) => { + var y = d3.scaleLinear().range([height, 0]); + y.domain([d3.min(data, (d) => {return d[dataName]}), d3.max(data, (d) => { return d[dataName]; })]); // Append the y2-axis to the far left and shift it over some distance - let YAxis2 = svg.append("g") - .attr("transform", `translate(-30, 0)`) // Move to the left and shift it by 20 units - .call(d3.axisLeft(y2)); - - YAxis2.selectAll("text") + let YAxis = svg.append("g") + .attr("transform", `translate(-${graphNumber * 37.5}, 0)`) // Move to the left and shift it by 20 units + .call(d3.axisLeft(y)); + + YAxis.selectAll("text") .style("fill", "#fcba03"); - - // add the Line + + // add the Line var valueLine = d3.line() - .x((d) => { return x1(d.tick); }) - .y((d) => { return y1(d.speed); }); + .x((d) => { return x(d.tick); }) + .y((d) => { return y(d[dataName]); }); svg.append("path") .data([data]) .attr("class", "line") .attr("fill", "none") - .attr("stroke", "steelblue") + .attr("stroke", "#fcba03") .attr("stroke-width", 1.5) .attr("d", valueLine) + graphNumber++; + }) } useEffect(() => { diff --git a/src/Subcomponents/MultiSelect.js b/src/Subcomponents/MultiSelect.js index 261e577..98d4c5e 100644 --- a/src/Subcomponents/MultiSelect.js +++ b/src/Subcomponents/MultiSelect.js @@ -38,9 +38,10 @@ export default function MultiSelect(props) { label="Graph To Display" onChange={handleChange} > - State of Charge - Speed - Distances + State of Charge + Speed + Distances + Delta Energy diff --git a/src/Subcomponents/Stats.js b/src/Subcomponents/Stats.js index be7fb62..e9f352d 100644 --- a/src/Subcomponents/Stats.js +++ b/src/Subcomponents/Stats.js @@ -24,7 +24,7 @@ class Stats extends Component { return(
    - +
    From 7cf3361de286886440a91e6ba18dd2aa96b114ab Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 4 Nov 2023 14:48:21 -0700 Subject: [PATCH 11/24] adding different colors to graphs --- src/Subcomponents/Graph.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index 9d6b8c8..e673bd3 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -7,7 +7,7 @@ export default function Graph(props) { const createGraph = async () => { // data array will store datapoints for the selected graphs let data = []; - let colors = ['#fcba03', "#6b03fc", "#cc0808", "#3bc708"] + let colors = ['#fcba03', "#9405fa", "#cc0808", "#3bc708"] // initializing data array to have the correct size and a tick value at each point. // TODO: Data tick values are not the corresponding simulation output ticks since simulation outputs have been shortened. props.data['speed_kmh'].forEach((value, tick) => { @@ -47,6 +47,7 @@ export default function Graph(props) { let graphNumber = 0; graphList.forEach((dataName) => { + var color = colors[graphNumber]; var y = d3.scaleLinear().range([height, 0]); y.domain([d3.min(data, (d) => {return d[dataName]}), d3.max(data, (d) => { return d[dataName]; })]); // Append the y2-axis to the far left and shift it over some distance @@ -55,7 +56,7 @@ export default function Graph(props) { .call(d3.axisLeft(y)); YAxis.selectAll("text") - .style("fill", "#fcba03"); + .style("fill", color); // add the Line var valueLine = d3.line() @@ -65,7 +66,7 @@ export default function Graph(props) { .data([data]) .attr("class", "line") .attr("fill", "none") - .attr("stroke", "#fcba03") + .attr("stroke", color) .attr("stroke-width", 1.5) .attr("d", valueLine) graphNumber++; From 7e74aa6f008ee046c5426c5a4b28e83f93377441 Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Thu, 9 Nov 2023 19:15:55 -0800 Subject: [PATCH 12/24] add hover to d3 graph --- src/App.css | 9 +++++++++ src/Subcomponents/Graph.js | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/App.css b/src/App.css index 3523f27..fed56fd 100644 --- a/src/App.css +++ b/src/App.css @@ -125,6 +125,15 @@ align-items: center; } +.hover_tooltip { + position: absolute; + background-color: white; + color: black; + padding: 5px; + border: 1px solid #ccc; + border-radius: 5px; + z-index: 1000; +} /* Zoom Chart Styling */ /* ----------------------------------- */ diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index bb9f41c..b0404e3 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -61,6 +61,7 @@ export default function Graph(props) { var valueLine = d3.line() .x((d) => { return x1(d.tick); }) .y((d) => { return y1(d.speed); }); + svg.append("path") .data([data]) .attr("class", "line") @@ -68,6 +69,32 @@ export default function Graph(props) { .attr("stroke", "steelblue") .attr("stroke-width", 1.5) .attr("d", valueLine) + + + // Define a tooltip element that will be shown on hover + const hover_tooltip = d3.select("body").append("div") + .attr("class", "hover_tooltip") + .style("opacity", 0); + + // Add a transparent overlay to capture hover events + svg.append("rect") + .attr("width", width) + .attr("height", height) + .style("fill", "none") + .style("pointer-events", "all") + .on("mouseover", () => hover_tooltip.style("opacity", "1")) + .on("mouseout", () => hover_tooltip.style("opacity", "0")) + .on("mousemove", (event) => { + // Calculate the corresponding data point based on the mouse position + const xPosition = x1.invert(d3.pointer(event)[0]); + const i = d3.bisectLeft(data.map(d => d.tick), xPosition, 1); + const dataPoint = data[i - 1]; + + // Show the tooltip with data + hover_tooltip.html(`Tick: ${dataPoint.tick}
    Speed: ${dataPoint.speed}
    SoC: ${dataPoint.soc}`) + .style("left", (event.pageX + 10) + "px") + .style("top", (event.pageY - 30) + "px"); + }); } useEffect(() => { From 0e406a5b773a02f2b04328bbaf0e990de736b26d Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Wed, 15 Nov 2023 22:55:14 -0800 Subject: [PATCH 13/24] made the tooltip text dynamic --- src/Subcomponents/Graph.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index b0404e3..021b56e 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -71,7 +71,7 @@ export default function Graph(props) { .attr("d", valueLine) - // Define a tooltip element that will be shown on hover + // Tooltip element that will be shown on hover const hover_tooltip = d3.select("body").append("div") .attr("class", "hover_tooltip") .style("opacity", 0); @@ -82,16 +82,22 @@ export default function Graph(props) { .attr("height", height) .style("fill", "none") .style("pointer-events", "all") - .on("mouseover", () => hover_tooltip.style("opacity", "1")) - .on("mouseout", () => hover_tooltip.style("opacity", "0")) + .on("mouseover", () => hover_tooltip.style("opacity", "1")) // show tooltip + .on("mouseout", () => hover_tooltip.style("opacity", "0")) // make tooltip invisible .on("mousemove", (event) => { // Calculate the corresponding data point based on the mouse position const xPosition = x1.invert(d3.pointer(event)[0]); const i = d3.bisectLeft(data.map(d => d.tick), xPosition, 1); const dataPoint = data[i - 1]; + let tooltip_str = ""; + + // Build tooltip string with data poitns rounded to 5 decimal places + Object.keys(dataPoint).forEach((key) => { + tooltip_str += key.charAt(0).toUpperCase() + key.slice(1) + " " + (Math.round(dataPoint[key] * 100000) / 100000) + "
    "; + }); - // Show the tooltip with data - hover_tooltip.html(`Tick: ${dataPoint.tick}
    Speed: ${dataPoint.speed}
    SoC: ${dataPoint.soc}`) + // Update tooltip text and position + hover_tooltip.html(tooltip_str) .style("left", (event.pageX + 10) + "px") .style("top", (event.pageY - 30) + "px"); }); @@ -104,6 +110,6 @@ export default function Graph(props) { d3.select('#graphBox svg').remove(); }; }, []); - + return(<>); } \ No newline at end of file From 95d45e73b4494d3d89b3f11d2bc88779be015407 Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Thu, 16 Nov 2023 18:40:48 -0800 Subject: [PATCH 14/24] rebasing --- src/App.js | 1 + src/Subcomponents/Graph.js | 33 ++++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/App.js b/src/App.js index 9aaaafa..926a9e8 100644 --- a/src/App.js +++ b/src/App.js @@ -162,6 +162,7 @@ class App extends Component { this.setState({ ExtraGraphs: typeof value === 'string' ? value.split(',') : value, }); + } diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index e8a3f23..ad2be86 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -4,7 +4,7 @@ import React, {useEffect, useRef, useMemo, useState} from "react"; export default function Graph(props) { const [graphList, setGraphList] = useState(props.graphs); - const createGraph = async () => { + const createGraph = () => { // data array will store datapoints for the selected graphs let data = []; let colors = ['#fcba03', "#9405fa", "#cc0808", "#3bc708"] @@ -24,7 +24,7 @@ export default function Graph(props) { var leftMargin = graphList.length * 37.5; // set the dimensions and margins of the graph - var margin = { top: 20, right: 50, bottom: 50, left: leftMargin }; + var margin = { top: 30, right: 50, bottom: 50, left: leftMargin }; // Get the width of the "graphBox" div var graphBox = document.getElementById('graphBox'); @@ -52,7 +52,7 @@ export default function Graph(props) { y.domain([d3.min(data, (d) => {return d[dataName]}), d3.max(data, (d) => { return d[dataName]; })]); // Append the y2-axis to the far left and shift it over some distance let YAxis = svg.append("g") - .attr("transform", `translate(-${graphNumber * 37.5}, 0)`) // Move to the left and shift it by 20 units + .attr("transform", `translate(-${graphNumber * 40}, 0)`) // Move to the left and shift it by 20 units .call(d3.axisLeft(y)); YAxis.selectAll("text") @@ -69,8 +69,27 @@ export default function Graph(props) { .attr("stroke", color) .attr("stroke-width", 1.5) .attr("d", valueLine) - - graphNumber++; + + svg.selectAll("mydots") + .data([data]) + .enter() + .append("circle") + .attr("cx", d => 130 * graphNumber) // Adjust the x position to stack horizontally + .attr("cy", -20) // Keep the vertical position constant + .attr("r", 7) + .style("fill", color); + // Add one dot in the legend for each name. + svg.selectAll("mylabels") + .data([data]) + .enter() + .append("text") + .attr("x", d => 130 * graphNumber + 13) // Adjust the x position to stack horizontally + .attr("y", -20) // Keep the vertical position constant + .style("fill", color) + .text(dataName) + .attr("text-anchor", "left") + .style("alignment-baseline", "middle"); + graphNumber++; }) // Tooltip element that will be shown on hover @@ -111,7 +130,7 @@ export default function Graph(props) { // Cleanup code to remove the SVG element d3.select('#graphBox svg').remove(); }; - }, []); - + }, [props.graphs]); + return(<>); } \ No newline at end of file From 0b1629e06145fc4bc1a6370012ceaa7f9d126629 Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Thu, 16 Nov 2023 20:02:18 -0800 Subject: [PATCH 15/24] fixed update graph bug + refactor stat.js --- src/App.css | 2 +- src/Subcomponents/Graph.js | 17 ++++----- src/Subcomponents/Stats.js | 70 +++++++++++++++++--------------------- 3 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/App.css b/src/App.css index fed56fd..d9662ce 100644 --- a/src/App.css +++ b/src/App.css @@ -132,7 +132,7 @@ padding: 5px; border: 1px solid #ccc; border-radius: 5px; - z-index: 1000; + z-index: 99; } /* Zoom Chart Styling */ diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index e8a3f23..5d4c193 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -14,7 +14,7 @@ export default function Graph(props) { data[tick] = {tick: tick} }) - graphList.forEach((dataName) => { + props.graphs.forEach((dataName) => { let values1 = props.data[dataName]; values1.forEach((d, index) => { data[index][dataName] = d; @@ -22,7 +22,7 @@ export default function Graph(props) { }); - var leftMargin = graphList.length * 37.5; + var leftMargin = props.graphs.length * 37.5; // set the dimensions and margins of the graph var margin = { top: 20, right: 50, bottom: 50, left: leftMargin }; @@ -31,6 +31,9 @@ export default function Graph(props) { var width = graphBox.clientWidth - margin.left - margin.right; var height = graphBox.clientHeight - margin.top - margin.bottom; + // Clean up previous graph before creating a new one + d3.select('#graphBox svg').remove(); + // append the svg object to the body of the page var svg = d3.select("#graphBox").append("svg") .attr("width", width + margin.left + margin.right) @@ -46,7 +49,7 @@ export default function Graph(props) { .call(d3.axisBottom(x)); let graphNumber = 0; - graphList.forEach((dataName) => { + props.graphs.forEach((dataName) => { var color = colors[graphNumber]; var y = d3.scaleLinear().range([height, 0]); y.domain([d3.min(data, (d) => {return d[dataName]}), d3.max(data, (d) => { return d[dataName]; })]); @@ -106,12 +109,10 @@ export default function Graph(props) { } useEffect(() => { + // Clean up previous graph before creating a new one createGraph(); - return () => { - // Cleanup code to remove the SVG element - d3.select('#graphBox svg').remove(); - }; - }, []); + + }, [props.graphs]); return(<>); } \ No newline at end of file diff --git a/src/Subcomponents/Stats.js b/src/Subcomponents/Stats.js index e9f352d..0412c09 100644 --- a/src/Subcomponents/Stats.js +++ b/src/Subcomponents/Stats.js @@ -1,47 +1,41 @@ -import React, { Component } from 'react'; -import ZoomChart from './ZoomChart' -import Graph from './Graph' -import secondsToDhms from "../HelperFunctions/TimeString" - +import React, { useEffect } from 'react'; +import ZoomChart from './ZoomChart'; +import Graph from './Graph'; +import secondsToDhms from '../HelperFunctions/TimeString'; import '../App.css'; -import loading from "../Images/loading.gif" +import loading from '../Images/loading.gif'; import MultiSelect from './MultiSelect'; -class Stats extends Component { - createGraph(arrayName) { - return( - - ) - } - - render() { - let returnString = () => { - let emptyString = "NO DATA..." - if(this.props.loading){ - return loading - } else { - if (this.props.json["empty"] === undefined){ - return( -
    -
    - -
    +const Stats = (props) => { + const createGraph = (arrayName) => { + return ; + }; -
    - ); - } else { - return
    {emptyString}
    ; - } - } - } - - return( -
    - - {returnString()} + const returnString = () => { + let emptyString = 'NO DATA...'; + if (props.loading) { + return loading; + } else { + if (props.json['empty'] === undefined) { + return ( +
    +
    +
    +
    ); + } else { + return
    {emptyString}
    ; + } } -} + }; + + return ( +
    + + {returnString()} +
    + ); +}; export default Stats; From 71b78d72c47900995e6336c52278011057507c95 Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Sat, 18 Nov 2023 11:38:56 -0800 Subject: [PATCH 16/24] added a vertical line to follow mouse x-coord hover --- src/Subcomponents/Graph.js | 62 +++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index b963c36..b96d4a8 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -100,30 +100,50 @@ export default function Graph(props) { .attr("class", "hover_tooltip") .style("opacity", 0); + // Add a vertical line that follows the mouse + const hoverLine = svg.append("line") + .attr("class", "hover-line") + .style("stroke", "#666") + .style("stroke-width", "2px") + .style("stroke-dasharray", "3,3") + .attr("x1", 0) + .attr("x2", 0) + .attr("y1", 0) + .attr("y2", height) + .style("opacity", 0); + // Add a transparent overlay to capture hover events svg.append("rect") - .attr("width", width) - .attr("height", height) - .style("fill", "none") - .style("pointer-events", "all") - .on("mouseover", () => hover_tooltip.style("opacity", "1")) // show tooltip - .on("mouseout", () => hover_tooltip.style("opacity", "0")) // make tooltip invisible - .on("mousemove", (event) => { - // Calculate the corresponding data point based on the mouse position - const xPosition = x.invert(d3.pointer(event)[0]); - const i = d3.bisectLeft(data.map(d => d.tick), xPosition, 1); - const dataPoint = data[i - 1]; - let tooltip_str = ""; - - // Build tooltip string with data poitns rounded to 5 decimal places - Object.keys(dataPoint).forEach((key) => { - tooltip_str += key + ": " + (Math.round(dataPoint[key] * 100000) / 100000) + "
    "; - }); + .attr("width", width) + .attr("height", height) + .style("fill", "none") + .style("pointer-events", "all") + .on("mouseover", () => { + hover_tooltip.style("opacity", 1); + hoverLine.style("opacity", 1); + }) // show tooltip and horizontal line + .on("mouseout", () => { + hover_tooltip.style("opacity", 0); + hoverLine.style("opacity", 0); + }) // make tooltip and horizontal line invisible + .on("mousemove", (event) => { + // Calculate the corresponding data point based on the mouse position + const xPosition = d3.pointer(event)[0]; + const xPositionInverted = x.invert(xPosition); + const i = d3.bisectLeft(data.map(d => d.tick), xPositionInverted, 1); + const dataPoint = data[i - 1]; + + // Update tooltip text and position + let tooltip_str = ""; + Object.keys(dataPoint).forEach((key) => { + tooltip_str += key + ": " + (Math.round(dataPoint[key] * 100000) / 100000) + "
    "; + }); + hover_tooltip.html(tooltip_str) + .style("left", (event.pageX + 10) + "px") + .style("top", (event.pageY - 30) + "px"); - // Update tooltip text and position - hover_tooltip.html(tooltip_str) - .style("left", (event.pageX + 10) + "px") - .style("top", (event.pageY - 30) + "px"); + // Update horizontal line position + hoverLine.attr("x1", xPosition).attr("x2", xPosition); }); } From d34950053c977edec2b2a6c0b7051194a4f50d24 Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Sat, 18 Nov 2023 13:53:07 -0800 Subject: [PATCH 17/24] added a horizontal line on mouse hover to make crosshair --- src/Subcomponents/Graph.js | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index b96d4a8..dc0a5f3 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -100,8 +100,8 @@ export default function Graph(props) { .attr("class", "hover_tooltip") .style("opacity", 0); - // Add a vertical line that follows the mouse - const hoverLine = svg.append("line") + // Define lines to create a crosshair that follows the mouse + const hoverLineVertical = svg.append("line") .attr("class", "hover-line") .style("stroke", "#666") .style("stroke-width", "2px") @@ -112,6 +112,17 @@ export default function Graph(props) { .attr("y2", height) .style("opacity", 0); + const hoverLineHorizontal = svg.append("line") + .attr("class", "hover-line") + .style("stroke", "#666") + .style("stroke-width", "2px") + .style("stroke-dasharray", "3,3") + .attr("x1", 0) + .attr("x2", width) + .attr("y1", 0) + .attr("y2", 0) + .style("opacity", 0); + // Add a transparent overlay to capture hover events svg.append("rect") .attr("width", width) @@ -119,13 +130,17 @@ export default function Graph(props) { .style("fill", "none") .style("pointer-events", "all") .on("mouseover", () => { + // show tooltip and horizontal line hover_tooltip.style("opacity", 1); - hoverLine.style("opacity", 1); - }) // show tooltip and horizontal line + hoverLineVertical.style("opacity", 1); + hoverLineHorizontal.style("opacity", 1); + }) .on("mouseout", () => { + // make tooltip and horizontal line invisible hover_tooltip.style("opacity", 0); - hoverLine.style("opacity", 0); - }) // make tooltip and horizontal line invisible + hoverLineVertical.style("opacity", 0); + hoverLineHorizontal.style("opacity", 0); + }) .on("mousemove", (event) => { // Calculate the corresponding data point based on the mouse position const xPosition = d3.pointer(event)[0]; @@ -142,8 +157,10 @@ export default function Graph(props) { .style("left", (event.pageX + 10) + "px") .style("top", (event.pageY - 30) + "px"); - // Update horizontal line position - hoverLine.attr("x1", xPosition).attr("x2", xPosition); + // Update crosshair position + const yPosition = d3.pointer(event)[1]; + hoverLineHorizontal.attr("y1", yPosition).attr("y2", yPosition); + hoverLineVertical.attr("x1", xPosition).attr("x2", xPosition); }); } From 422f2911ffad55ee8090302f23a4a47978a3f24f Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 18 Nov 2023 13:59:15 -0800 Subject: [PATCH 18/24] adding zoom functionality --- src/Subcomponents/Graph.js | 129 ++++++++++++++++++++++++++++++++----- 1 file changed, 114 insertions(+), 15 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index dc0a5f3..309758a 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -2,7 +2,6 @@ import * as d3 from "d3"; import React, {useEffect, useRef, useMemo, useState} from "react"; export default function Graph(props) { - const [graphList, setGraphList] = useState(props.graphs); const createGraph = () => { // data array will store datapoints for the selected graphs @@ -44,40 +43,97 @@ export default function Graph(props) { var x = d3.scaleLinear().range([0, width]); x.domain(d3.extent(data, (d) => { return d.tick; })); - svg.append("g") + var xAxis = svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x)); + + + + + // Add a clipPath: everything out of this area won't be drawn. + var clip = svg.append("defs").append("svg:clipPath") + .attr("id", "clip") + .append("svg:rect") + .attr("width", width ) + .attr("height", height ) + .attr("x", 0) + .attr("y", 0); + + // Tooltip element that will be shown on hover + const hover_tooltip = d3.select("body").append("div") + .attr("class", "hover_tooltip") + .style("opacity", 0); + + // Add brushing + var brush = d3.brushX() // Add the brush feature using the d3.brush function + .extent( [ [0,0], [width,height] ] ) // initialise the brush area: start at 0,0 and finishes at width,height: it means I select the whole graph area + .on("end", (event, d) => {updateChart(event, d)}) // Each time the brush selection changes, trigger the 'updateChart' function + + // Create the line variable: where both the line and the brush take place + var line = svg.append('g') + .attr("clip-path", "url(#clip)") + + // Add the brushing + line + .append("g") + .attr("class", "brush") + .call(brush); + + var y = d3.scaleLinear().range([height, 0]); + let graphNumber = 0; props.graphs.forEach((dataName) => { var color = colors[graphNumber]; - var y = d3.scaleLinear().range([height, 0]); + y.domain([d3.min(data, (d) => {return d[dataName]}), d3.max(data, (d) => { return d[dataName]; })]); // Append the y2-axis to the far left and shift it over some distance - let YAxis = svg.append("g") + var YAxis = svg.append("g") .attr("transform", `translate(-${graphNumber * 40}, 0)`) // Move to the left and shift it by 20 units .call(d3.axisLeft(y)); YAxis.selectAll("text") .style("fill", color); - // add the Line + // add the Line var valueLine = d3.line() .x((d) => { return x(d.tick); }) .y((d) => { return y(d[dataName]); }); - svg.append("path") + + + line.append("path") .data([data]) - .attr("class", "line") + .attr("class", `line-${graphNumber+1}`) .attr("fill", "none") .attr("stroke", color) - .attr("stroke-width", 1.5) + .attr("stroke-width", 3) .attr("d", valueLine) + .on("mousemove", (event) => { + // Calculate the corresponding data point based on the mouse position + const xPosition = x.invert(d3.pointer(event)[0]); + const i = d3.bisectLeft(data.map(d => d.tick), xPosition, 1); + const dataPoint = data[i - 1]; + let tooltip_str = ""; + + // Build tooltip string with data points rounded to 5 decimal places + Object.keys(dataPoint).forEach((key) => { + tooltip_str += key + ": " + (Math.round(dataPoint[key] * 100000) / 100000) + "
    "; + }); + + // Update tooltip text and position + hover_tooltip.html(tooltip_str) + .style("left", (event.pageX + 10) + "px") + .style("top", (event.pageY - 30) + "px"); + }) + .on("mouseover", () => hover_tooltip.style("opacity", "1")) // show tooltip + .on("mouseout", () => hover_tooltip.style("opacity", "0")); // make tooltip invisible + svg.selectAll("mydots") .data([data]) .enter() .append("circle") - .attr("cx", d => 130 * graphNumber) // Adjust the x position to stack horizontally + .attr("cx", d => 160 * graphNumber - (props.graphs.length * 40) + 40) // Adjust the x position to stack horizontally .attr("cy", -20) // Keep the vertical position constant .attr("r", 7) .style("fill", color); @@ -86,7 +142,7 @@ export default function Graph(props) { .data([data]) .enter() .append("text") - .attr("x", d => 130 * graphNumber + 13) // Adjust the x position to stack horizontally + .attr("x", d => 160 * graphNumber + 13 - (props.graphs.length * 40) + 40) // Adjust the x position to stack horizontally .attr("y", -20) // Keep the vertical position constant .style("fill", color) .text(dataName) @@ -95,11 +151,6 @@ export default function Graph(props) { graphNumber++; }) - // Tooltip element that will be shown on hover - const hover_tooltip = d3.select("body").append("div") - .attr("class", "hover_tooltip") - .style("opacity", 0); - // Define lines to create a crosshair that follows the mouse const hoverLineVertical = svg.append("line") .attr("class", "hover-line") @@ -162,6 +213,54 @@ export default function Graph(props) { hoverLineHorizontal.attr("y1", yPosition).attr("y2", yPosition); hoverLineVertical.attr("x1", xPosition).attr("x2", xPosition); }); + + // A function that set idleTimeOut to null + var idleTimeout + function idled() { idleTimeout = null; } + + function updateChart(event, d) { + // What are the selected boundaries? + let extent = event.selection; + + let maxWidth = props.data["speed_kmh"].length; + + // If no selection, back to the initial coordinate. Otherwise, update X axis domain + if (!extent) { + if (!idleTimeout) return (idleTimeout = setTimeout(idled, 350)); // This allows waiting a little bit + x.domain([0, maxWidth]); + } else { + x.domain([x.invert(extent[0]), x.invert(extent[1])]); + line.select(".brush").call(brush.move, null); // This removes the grey brush area as soon as the selection has been done + } + + // Update axis and line position + xAxis.transition().duration(1000).call(d3.axisBottom(x)); + + // Update each line separately + props.graphs.forEach((dataName, index) => { + let yScale = d3.scaleLinear().range([height, 0]); // Separate y-scale for each line + + yScale.domain([d3.min(data, (d) => d[dataName]), d3.max(data, (d) => d[dataName])]); + + let lineSelection = line.select(`.line-${index + 1}`); + lineSelection + .transition() + .duration(1000) + .attr("d", d3.line() + .x(function (d) { return x(d.tick); }) + .y(function (d) { return yScale(d[dataName]); }) + ); + + // Update y-axis position for each line + svg.select(`.y-axis-${index + 1}`) + .transition() + .duration(1000) + .call(d3.axisLeft(yScale)); + }) + } + + + } useEffect(() => { From d5f8e0e056095c7605a5219be0a035f591856f26 Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 18 Nov 2023 14:09:49 -0800 Subject: [PATCH 19/24] Integrating hover functionality with zoom functionality --- src/Subcomponents/Graph.js | 122 ++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 70 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index 309758a..7a3e608 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -59,11 +59,37 @@ export default function Graph(props) { .attr("x", 0) .attr("y", 0); + /** Hover Functionality **/ + // Tooltip element that will be shown on hover const hover_tooltip = d3.select("body").append("div") .attr("class", "hover_tooltip") .style("opacity", 0); + // Define lines to create a crosshair that follows the mouse + const hoverLineVertical = svg.append("line") + .attr("class", "hover-line") + .style("stroke", "#666") + .style("stroke-width", "2px") + .style("stroke-dasharray", "3,3") + .attr("x1", 0) + .attr("x2", 0) + .attr("y1", 0) + .attr("y2", height) + .style("opacity", 0); + + const hoverLineHorizontal = svg.append("line") + .attr("class", "hover-line") + .style("stroke", "#666") + .style("stroke-width", "2px") + .style("stroke-dasharray", "3,3") + .attr("x1", 0) + .attr("x2", width) + .attr("y1", 0) + .attr("y2", 0) + .style("opacity", 0); + + // Add brushing var brush = d3.brushX() // Add the brush feature using the d3.brush function @@ -108,25 +134,39 @@ export default function Graph(props) { .attr("stroke", color) .attr("stroke-width", 3) .attr("d", valueLine) + .on("mouseover", () => { + // show tooltip and horizontal line + hover_tooltip.style("opacity", 1); + hoverLineVertical.style("opacity", 1); + hoverLineHorizontal.style("opacity", 1); + }) + .on("mouseout", () => { + // make tooltip and horizontal line invisible + hover_tooltip.style("opacity", 0); + hoverLineVertical.style("opacity", 0); + hoverLineHorizontal.style("opacity", 0); + }) .on("mousemove", (event) => { // Calculate the corresponding data point based on the mouse position - const xPosition = x.invert(d3.pointer(event)[0]); - const i = d3.bisectLeft(data.map(d => d.tick), xPosition, 1); + const xPosition = d3.pointer(event)[0]; + const xPositionInverted = x.invert(xPosition); + const i = d3.bisectLeft(data.map(d => d.tick), xPositionInverted, 1); const dataPoint = data[i - 1]; + + // Update tooltip text and position let tooltip_str = ""; - - // Build tooltip string with data points rounded to 5 decimal places Object.keys(dataPoint).forEach((key) => { tooltip_str += key + ": " + (Math.round(dataPoint[key] * 100000) / 100000) + "
    "; }); - - // Update tooltip text and position hover_tooltip.html(tooltip_str) .style("left", (event.pageX + 10) + "px") .style("top", (event.pageY - 30) + "px"); - }) - .on("mouseover", () => hover_tooltip.style("opacity", "1")) // show tooltip - .on("mouseout", () => hover_tooltip.style("opacity", "0")); // make tooltip invisible + + // Update crosshair position + const yPosition = d3.pointer(event)[1]; + hoverLineHorizontal.attr("y1", yPosition).attr("y2", yPosition); + hoverLineVertical.attr("x1", xPosition).attr("x2", xPosition); + }); svg.selectAll("mydots") @@ -151,70 +191,12 @@ export default function Graph(props) { graphNumber++; }) - // Define lines to create a crosshair that follows the mouse - const hoverLineVertical = svg.append("line") - .attr("class", "hover-line") - .style("stroke", "#666") - .style("stroke-width", "2px") - .style("stroke-dasharray", "3,3") - .attr("x1", 0) - .attr("x2", 0) - .attr("y1", 0) - .attr("y2", height) - .style("opacity", 0); - - const hoverLineHorizontal = svg.append("line") - .attr("class", "hover-line") - .style("stroke", "#666") - .style("stroke-width", "2px") - .style("stroke-dasharray", "3,3") - .attr("x1", 0) - .attr("x2", width) - .attr("y1", 0) - .attr("y2", 0) - .style("opacity", 0); - - // Add a transparent overlay to capture hover events - svg.append("rect") - .attr("width", width) - .attr("height", height) - .style("fill", "none") - .style("pointer-events", "all") - .on("mouseover", () => { - // show tooltip and horizontal line - hover_tooltip.style("opacity", 1); - hoverLineVertical.style("opacity", 1); - hoverLineHorizontal.style("opacity", 1); - }) - .on("mouseout", () => { - // make tooltip and horizontal line invisible - hover_tooltip.style("opacity", 0); - hoverLineVertical.style("opacity", 0); - hoverLineHorizontal.style("opacity", 0); - }) - .on("mousemove", (event) => { - // Calculate the corresponding data point based on the mouse position - const xPosition = d3.pointer(event)[0]; - const xPositionInverted = x.invert(xPosition); - const i = d3.bisectLeft(data.map(d => d.tick), xPositionInverted, 1); - const dataPoint = data[i - 1]; + - // Update tooltip text and position - let tooltip_str = ""; - Object.keys(dataPoint).forEach((key) => { - tooltip_str += key + ": " + (Math.round(dataPoint[key] * 100000) / 100000) + "
    "; - }); - hover_tooltip.html(tooltip_str) - .style("left", (event.pageX + 10) + "px") - .style("top", (event.pageY - 30) + "px"); + - // Update crosshair position - const yPosition = d3.pointer(event)[1]; - hoverLineHorizontal.attr("y1", yPosition).attr("y2", yPosition); - hoverLineVertical.attr("x1", xPosition).attr("x2", xPosition); - }); + /** Zoom Functionality **/ - // A function that set idleTimeOut to null var idleTimeout function idled() { idleTimeout = null; } From ccf8ba1ae6cdf535a286fbde4a603d33c11cb1c4 Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 18 Nov 2023 14:32:47 -0800 Subject: [PATCH 20/24] cleaning up code, preparing for final push --- src/Subcomponents/Graph.js | 196 ++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index 7a3e608..60f954a 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -1,5 +1,5 @@ import * as d3 from "d3"; -import React, {useEffect, useRef, useMemo, useState} from "react"; +import React, {useEffect} from "react"; export default function Graph(props) { @@ -7,6 +7,7 @@ export default function Graph(props) { // data array will store datapoints for the selected graphs let data = []; let colors = ['#fcba03', "#9405fa", "#cc0808", "#3bc708"] + // initializing data array to have the correct size and a tick value at each point. // TODO: Data tick values are not the corresponding simulation output ticks since simulation outputs have been shortened. props.data['speed_kmh'].forEach((value, tick) => { @@ -20,7 +21,7 @@ export default function Graph(props) { }); }); - + // leftMargin is set dynamically to leave enough space for multiple y-axis var leftMargin = props.graphs.length * 37.5; // set the dimensions and margins of the graph var margin = { top: 30, right: 50, bottom: 50, left: leftMargin }; @@ -40,24 +41,26 @@ export default function Graph(props) { .append("g") .attr("transform", `translate(${margin.left}, ${margin.top})`); - + // Initialize x and yto the proper number of pixels var x = d3.scaleLinear().range([0, width]); + var y = d3.scaleLinear().range([height, 0]); + + // Initialize x-domain x.domain(d3.extent(data, (d) => { return d.tick; })); var xAxis = svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x)); - - - // Add a clipPath: everything out of this area won't be drawn. var clip = svg.append("defs").append("svg:clipPath") - .attr("id", "clip") - .append("svg:rect") - .attr("width", width ) - .attr("height", height ) - .attr("x", 0) - .attr("y", 0); + .attr("id", "clip") + .append("svg:rect") + .attr("width", width ) + .attr("height", height ) + .attr("x", 0) + .attr("y", 0); + + /** Hover Functionality **/ @@ -67,34 +70,83 @@ export default function Graph(props) { .style("opacity", 0); // Define lines to create a crosshair that follows the mouse - const hoverLineVertical = svg.append("line") - .attr("class", "hover-line") - .style("stroke", "#666") - .style("stroke-width", "2px") - .style("stroke-dasharray", "3,3") - .attr("x1", 0) - .attr("x2", 0) - .attr("y1", 0) - .attr("y2", height) - .style("opacity", 0); - - const hoverLineHorizontal = svg.append("line") - .attr("class", "hover-line") - .style("stroke", "#666") - .style("stroke-width", "2px") - .style("stroke-dasharray", "3,3") - .attr("x1", 0) - .attr("x2", width) - .attr("y1", 0) - .attr("y2", 0) - .style("opacity", 0); + const hoverLineVertical = svg.append("line") + .attr("class", "hover-line") + .style("stroke", "#666") + .style("stroke-width", "2px") + .style("stroke-dasharray", "3,3") + .attr("x1", 0) + .attr("x2", 0) + .attr("y1", 0) + .attr("y2", height) + .style("opacity", 0); + + const hoverLineHorizontal = svg.append("line") + .attr("class", "hover-line") + .style("stroke", "#666") + .style("stroke-width", "2px") + .style("stroke-dasharray", "3,3") + .attr("x1", 0) + .attr("x2", width) + .attr("y1", 0) + .attr("y2", 0) + .style("opacity", 0); + + /** Zoom Functionality **/ + + var idleTimeout + function idled() { idleTimeout = null; } + + // This function updates the chart based on the selected x-domain of the brush object. + function updateChart(event, d) { + // What are the selected boundaries? + let extent = event.selection; + + // number of ticks on the x-axis + let maxWidth = props.data["speed_kmh"].length; + + // If no selection, back to the initial coordinate. Otherwise, update X axis domain + if (!extent) { + if (!idleTimeout) return (idleTimeout = setTimeout(idled, 350)); // This allows waiting a little bit + x.domain([0, maxWidth]); + } else { + x.domain([x.invert(extent[0]), x.invert(extent[1])]); + line.select(".brush").call(brush.move, null); // This removes the grey brush area as soon as the selection has been done + } + // Update axis and line position + xAxis.transition().duration(1000).call(d3.axisBottom(x)); + + // Update each line separately + props.graphs.forEach((dataName, index) => { + let yScale = d3.scaleLinear().range([height, 0]); // Separate y-scale for each line + + yScale.domain([d3.min(data, (d) => d[dataName]), d3.max(data, (d) => d[dataName])]); + + let lineSelection = line.select(`.line-${index + 1}`); + lineSelection + .transition() + .duration(1000) + .attr("d", d3.line() + .x(function (d) { return x(d.tick); }) + .y(function (d) { return yScale(d[dataName]); }) + ); + + // Update y-axis position for each line + svg.select(`.y-axis-${index + 1}`) + .transition() + .duration(1000) + .call(d3.axisLeft(yScale)); + }) + } + + // Add brushing - var brush = d3.brushX() // Add the brush feature using the d3.brush function - .extent( [ [0,0], [width,height] ] ) // initialise the brush area: start at 0,0 and finishes at width,height: it means I select the whole graph area - .on("end", (event, d) => {updateChart(event, d)}) // Each time the brush selection changes, trigger the 'updateChart' function + var brush = d3.brushX() + .extent([[0,0], [width,height]]) + .on("end", (event, d) => {updateChart(event, d)}) // Create the line variable: where both the line and the brush take place var line = svg.append('g') @@ -105,34 +157,32 @@ export default function Graph(props) { .append("g") .attr("class", "brush") .call(brush); - - var y = d3.scaleLinear().range([height, 0]); let graphNumber = 0; props.graphs.forEach((dataName) => { var color = colors[graphNumber]; - y.domain([d3.min(data, (d) => {return d[dataName]}), d3.max(data, (d) => { return d[dataName]; })]); - // Append the y2-axis to the far left and shift it over some distance + + // Append the y-axis to the far left and shift it over some distance var YAxis = svg.append("g") - .attr("transform", `translate(-${graphNumber * 40}, 0)`) // Move to the left and shift it by 20 units + .attr("transform", `translate(-${graphNumber * 40}, 0)`) // Move to the left and shift it by 40 units .call(d3.axisLeft(y)); YAxis.selectAll("text") .style("fill", color); - // add the Line + // create the line var valueLine = d3.line() .x((d) => { return x(d.tick); }) .y((d) => { return y(d[dataName]); }); - - + + // add the line to the line object. Also introduce hover functionality on the line line.append("path") .data([data]) .attr("class", `line-${graphNumber+1}`) .attr("fill", "none") .attr("stroke", color) - .attr("stroke-width", 3) + .attr("stroke-width", 4) // change this to change thickness of graph lines .attr("d", valueLine) .on("mouseover", () => { // show tooltip and horizontal line @@ -168,7 +218,7 @@ export default function Graph(props) { hoverLineVertical.attr("x1", xPosition).attr("x2", xPosition); }); - + // These are the dots and labels for the legend at the top of the graph svg.selectAll("mydots") .data([data]) .enter() @@ -177,6 +227,7 @@ export default function Graph(props) { .attr("cy", -20) // Keep the vertical position constant .attr("r", 7) .style("fill", color); + // Add one dot in the legend for each name. svg.selectAll("mylabels") .data([data]) @@ -188,67 +239,16 @@ export default function Graph(props) { .text(dataName) .attr("text-anchor", "left") .style("alignment-baseline", "middle"); + graphNumber++; }) - - - - - /** Zoom Functionality **/ - - var idleTimeout - function idled() { idleTimeout = null; } - - function updateChart(event, d) { - // What are the selected boundaries? - let extent = event.selection; - - let maxWidth = props.data["speed_kmh"].length; - - // If no selection, back to the initial coordinate. Otherwise, update X axis domain - if (!extent) { - if (!idleTimeout) return (idleTimeout = setTimeout(idled, 350)); // This allows waiting a little bit - x.domain([0, maxWidth]); - } else { - x.domain([x.invert(extent[0]), x.invert(extent[1])]); - line.select(".brush").call(brush.move, null); // This removes the grey brush area as soon as the selection has been done - } - - // Update axis and line position - xAxis.transition().duration(1000).call(d3.axisBottom(x)); - - // Update each line separately - props.graphs.forEach((dataName, index) => { - let yScale = d3.scaleLinear().range([height, 0]); // Separate y-scale for each line - - yScale.domain([d3.min(data, (d) => d[dataName]), d3.max(data, (d) => d[dataName])]); - - let lineSelection = line.select(`.line-${index + 1}`); - lineSelection - .transition() - .duration(1000) - .attr("d", d3.line() - .x(function (d) { return x(d.tick); }) - .y(function (d) { return yScale(d[dataName]); }) - ); - - // Update y-axis position for each line - svg.select(`.y-axis-${index + 1}`) - .transition() - .duration(1000) - .call(d3.axisLeft(yScale)); - }) - } - - } useEffect(() => { // Clean up previous graph before creating a new one createGraph(); - }, [props.graphs]); return(<>); From 05ed8a1d6b3e9ef477515f9a892a2bf2e7ed6813 Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 18 Nov 2023 14:37:28 -0800 Subject: [PATCH 21/24] slight style fix --- src/Subcomponents/Graph.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index 60f954a..927b42f 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -242,8 +242,6 @@ export default function Graph(props) { graphNumber++; }) - - } useEffect(() => { From 1222fd4c143589e58f47d99e41402b4b0c916193 Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 18 Nov 2023 14:37:47 -0800 Subject: [PATCH 22/24] slight style fix 2 --- src/Subcomponents/Graph.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index 927b42f..da9c18d 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -142,7 +142,6 @@ export default function Graph(props) { }) } - // Add brushing var brush = d3.brushX() .extent([[0,0], [width,height]]) From 5b8da44e443ad7284e90273c4445ad7a7d43e3ea Mon Sep 17 00:00:00 2001 From: Dekempsy4 Date: Sat, 18 Nov 2023 14:44:29 -0800 Subject: [PATCH 23/24] removing unecessary code block, updating TODO at the top for future projects --- src/Subcomponents/Graph.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index da9c18d..b3c260f 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -3,13 +3,16 @@ import React, {useEffect} from "react"; export default function Graph(props) { + // TODO: Data tick values are not the corresponding simulation output ticks since simulation outputs have been shortened. + // TODO: Hunt down the ellusive bug. The hover functionality sometimes randomly disappears. Sometimes reappearing shortly + // and sometimes staying gone for good. This isn't a huge problem but one day should be fixed + const createGraph = () => { // data array will store datapoints for the selected graphs let data = []; let colors = ['#fcba03', "#9405fa", "#cc0808", "#3bc708"] // initializing data array to have the correct size and a tick value at each point. - // TODO: Data tick values are not the corresponding simulation output ticks since simulation outputs have been shortened. props.data['speed_kmh'].forEach((value, tick) => { data[tick] = {tick: tick} }) @@ -133,12 +136,6 @@ export default function Graph(props) { .x(function (d) { return x(d.tick); }) .y(function (d) { return yScale(d[dataName]); }) ); - - // Update y-axis position for each line - svg.select(`.y-axis-${index + 1}`) - .transition() - .duration(1000) - .call(d3.axisLeft(yScale)); }) } From 499c110a9cf0ae5836bbadda2084b5b0f93e8cba Mon Sep 17 00:00:00 2001 From: tamzeedq <74509993+tamzeedq@users.noreply.github.com> Date: Thu, 23 Nov 2023 19:26:48 -0800 Subject: [PATCH 24/24] small style change, fixed bug where graph needs to update when window resizes --- src/App.css | 4 ++++ src/Subcomponents/Graph.js | 17 ++++++++++++++++- src/Subcomponents/ValueTable.js | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/App.css b/src/App.css index d9662ce..6cb2e86 100644 --- a/src/App.css +++ b/src/App.css @@ -81,6 +81,10 @@ border: 1px solid grey; } +#valueTable { + margin-top: 20px; +} + /* Stat Bar Styles */ /* ----------------------------------- */ diff --git a/src/Subcomponents/Graph.js b/src/Subcomponents/Graph.js index b3c260f..47b4509 100644 --- a/src/Subcomponents/Graph.js +++ b/src/Subcomponents/Graph.js @@ -178,7 +178,7 @@ export default function Graph(props) { .attr("class", `line-${graphNumber+1}`) .attr("fill", "none") .attr("stroke", color) - .attr("stroke-width", 4) // change this to change thickness of graph lines + .attr("stroke-width", 2) // change this to change thickness of graph lines .attr("d", valueLine) .on("mouseover", () => { // show tooltip and horizontal line @@ -243,6 +243,21 @@ export default function Graph(props) { useEffect(() => { // Clean up previous graph before creating a new one createGraph(); + + + // Listenser to remake/rerender the graph when the size of the window changes + // Add event listener for window resize + const handleResize = () => { + createGraph(); + }; + + window.addEventListener('resize', handleResize); + + // Remove the event listener when the component unmounts + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [props.graphs]); return(<>); diff --git a/src/Subcomponents/ValueTable.js b/src/Subcomponents/ValueTable.js index d1ed651..a9a7d07 100644 --- a/src/Subcomponents/ValueTable.js +++ b/src/Subcomponents/ValueTable.js @@ -12,7 +12,7 @@ class ValueTable extends Component { const { currentValues, expectedValues } = this.props; return ( - +