Skip to content

Commit

Permalink
User/evliu/temp deploy (#1068)
Browse files Browse the repository at this point in the history
### Summary
<!-- Quick summary of changes, optional -->

### Changelist 
<!-- Give a list of the changes covered in this PR. This will help both
you and the reviewer keep this PR within scope. -->

### Testing Done
<!-- Outline the testing that was done to demonstrate the changes are
solid. This could be unit tests, integration tests, testing on the car,
etc. Include relevant code snippets, screenshots, etc as needed. -->

### Resolved Issues
<!-- Link any issues that this PR resolved like so: `Resolves #1, #2,
and #5` (Note: Using this format, Github will automatically close the
issue(s) when this PR is merged in). -->

### Checklist
*Please change `[ ]` to `[x]` when you are ready.*
- [x] I have read and followed the code conventions detailed in
[README.md](../README.md) (*This will save time for both you and the
reviewer!*).
- [x] If this pull request is longer then **500** lines, I have provided
*explicit* justification in the summary above explaining why I *cannot*
break this up into multiple pull requests (*Small PR's are faster and
less painful for everyone involved!*).

---------

Co-authored-by: Matthew Yung <[email protected]>
  • Loading branch information
Evanyl and myung03 authored Nov 4, 2023
1 parent 5717b2b commit ef4d7df
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 1,611 deletions.
1,165 changes: 108 additions & 1,057 deletions software/tracksight/frontend/package-lock.json

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions software/tracksight/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,24 @@
"@types/react": "18.2.22",
"@types/react-dom": "18.2.7",
"antd": "^5.9.2",
"buffer": "^6.0.3",
"bufferutil": "^4.0.7",
"dayjs": "^1.11.10",
"eslint": "8.49.0",
"eslint-config-next": "13.5.2",
"moment-timezone": "^0.5.43",
"next": "13.5.4",
"package.json": "^2.0.1",
"plotly.js": "^2.26.0",
"package.json": "^0.0.0",
"plotly.js": "^2.27.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-plotly.js": "^2.6.0",
"socket.io-client": "^4.7.2",
"typescript": "5.2.2",
"utf-8-validate": "^6.0.3"
},
"devDependencies": {
"@types/plotly.js": "^2.12.29",
"@types/react-plotly.js": "^2.6.2"
}
}
17 changes: 11 additions & 6 deletions software/tracksight/frontend/src/app/components/dropdown_menu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, Dispatch, SetStateAction } from 'react';
import { Select } from 'antd';

const DropdownMenu = (props) => {
const [items, setItems] = useState([]);
export interface DropdownMenuProps {
setOption: Dispatch<SetStateAction<string[]>>,
selectedOptions: string[],
options: string[],
single: boolean,
name: string,
}

const DropdownMenu = (props: DropdownMenuProps) => {
const [items, setItems] = useState<Array<{value: string, label: string}>>([]);

useEffect(() => {
const updatedItems = props.options.map((name, index) => ({
Expand Down Expand Up @@ -33,7 +41,4 @@ const DropdownMenu = (props) => {
);
};

DropdownMenu.defaultProps = {
single: false
};
export default DropdownMenu;
44 changes: 28 additions & 16 deletions software/tracksight/frontend/src/app/components/graph.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
'use client';

import { useState, useEffect } from 'react';
import Plot from 'react-plotly.js';
import { useState, useEffect, Dispatch, MouseEventHandler, SetStateAction } from 'react';
import Plotly, { PlotRelayoutEvent } from 'plotly.js';
import createPlotlyComponent from "react-plotly.js/factory";
const Plot = createPlotlyComponent(Plotly);
import { Card, Button } from 'antd';

import QueryData from './query_data.tsx';
import QueryData from './query_data';
import { MessageInstance } from 'antd/es/message/interface';

const DEFAULT_LAYOUT = {
const DEFAULT_LAYOUT: Partial<Plotly.Layout> = {
width: 620,
height: 500,
title: "Empty",
Expand All @@ -15,12 +18,23 @@ const DEFAULT_LAYOUT = {
legend: {"orientation": "h"},
}

const Graph = (props) => {
const [data, setData] = useState({});
const [formattedData, setFormattedData] = useState([]);
export interface GraphProps {
graphid: string,
id: string,
url: string,
sync: boolean,
setZoomData: Dispatch<SetStateAction<PlotRelayoutEvent>>
zoomData: PlotRelayoutEvent,
onDelete: MouseEventHandler<HTMLElement>,
messageApi: MessageInstance,
}

const Graph = (props: GraphProps) => {
const [data, setData] = useState<{[name: string]: {time: Array<string>, value: Array<number>}}>({});
const [formattedData, setFormattedData] = useState<Plotly.Data[]>([]);

//default graph layout
const [graphLayout, setGraphLayout] = useState(DEFAULT_LAYOUT);
const [graphLayout, setGraphLayout] = useState<Partial<Plotly.Layout>>(DEFAULT_LAYOUT);

// randomizes colour for graph lines
const getRandomColor = () => {
Expand All @@ -42,13 +56,13 @@ const Graph = (props) => {
// currently rerendering entire graph everytime there is zoom/change in signal. Not ideal in terms of performance,
// suggestions for improvements appreciated.
useEffect(() => {
const tempFormattedData = [];
const tempFormattedData: Plotly.Data[] = [];
for (const name in data) {
let signalData = data[name];
let xData = signalData["time"];
let yData = signalData["value"];

const formattedObj = {
const formattedObj: Plotly.Data = {
x: xData,
y: yData,
type: 'scatter',
Expand Down Expand Up @@ -91,20 +105,19 @@ const Graph = (props) => {
}
}, [props.zoomData]);

const handleZoom = (e) => {
const handleZoom = (e: Readonly<PlotRelayoutEvent>) => {
props.setZoomData(e);
}


return (
<Card
bodyStyle={{ display: 'flex', flexDirection: 'column' }}>
<QueryData url={props.url} setData={setData} messageApi={props.messageApi}></QueryData>
<Plot
data={formattedData} // Pass the array of formatted data objects
layout={graphLayout}
config={{
displayModeBar: true,
config={{
displayModeBar: true,
displaylogo: false,
scrollZoom: true,
}}
Expand All @@ -117,5 +130,4 @@ const Graph = (props) => {
);
}

export default Graph;

export default Graph;
9 changes: 6 additions & 3 deletions software/tracksight/frontend/src/app/components/navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import * as React from 'react';

import { Menu } from 'antd';
import { Dispatch, SetStateAction } from 'react';

export interface NavBarProps {
updateFunction: Dispatch<SetStateAction<string>>,
}

const NavBar = (props) => (
const NavBar = (props: NavBarProps) => (
<Menu
theme="light"
mode="horizontal"
Expand Down
73 changes: 37 additions & 36 deletions software/tracksight/frontend/src/app/components/query_data.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
'use client';

import { useEffect, useState } from 'react';
import { Dispatch, useEffect, useState } from 'react';
import { Button, Space } from 'antd';

import DropdownMenu from './dropdown_menu.tsx';
import TimeStampPicker from './timestamp_picker.tsx';
import DropdownMenu from './dropdown_menu';
import TimeStampPicker from './timestamp_picker';
import { MessageInstance } from 'antd/es/message/interface';

const QueryData = (props) => {
const [measurement, setMeasurement] = useState([]);
const [allMeasurements, setAllMeasurements] = useState([]);
export interface QueryDataProps {
url: string,
setData: Dispatch<{[name: string]: {time: Array<string>, value: Array<number>}}>,
messageApi: MessageInstance,
}

const [fields, setFields] = useState([]);
const [allFields, setAllFields] = useState([]);
const QueryData = (props: QueryDataProps) => {
const [measurement, setMeasurement] = useState<string[]>([]);
const [allMeasurements, setAllMeasurements] = useState<string[]>([]);

const [startEpoch, setStartEpoch] = useState(null);
const [endEpoch, setEndEpoch] = useState(null);
const [fields, setFields] = useState<string[]>([]);
const [allFields, setAllFields] = useState<string[]>([]);

useEffect(() => {
fetch(props.url + "/signal", {
method: 'GET',
}).then((response) => response.json())
.then((data) => setAvail(data))
.catch((error) => console.log(error));
const [startEpoch, setStartEpoch] = useState<string>("");
const [endEpoch, setEndEpoch] = useState<string>("");

useEffect(() => {
fetch(props.url + "/signal/measurement", {
method: 'get',
}).then((response) => response.json())
Expand All @@ -48,31 +49,31 @@ const QueryData = (props) => {
props.messageApi.open({type: "error", content: "Please fill out all fields properly"});
return;
}
fetch(props.url + "/query?" + new URLSearchParams({
'measurement': measurement[0],
'fields': fields,
'start_epoch': startEpoch,
'end_epoch': endEpoch,
})).then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
ok: response.ok,
json,
})));
}).then(({ status, json, ok }) => {
if (!ok) {
props.messageApi.open({type: "error", content: json["error"]})
} else {
props.setData(json);
}
}).catch((error) => console.log(error));

let urlWithSearchParams = new URLSearchParams(props.url + "/query");
urlWithSearchParams.append('measurement', measurement[0]);
for (const field in fields) {
urlWithSearchParams.append('fields', field);
}
urlWithSearchParams.append('start_epoch', startEpoch);
urlWithSearchParams.append('end_epoch', endEpoch);
fetch(urlWithSearchParams.toString())
.then((response) => {
if (!response.ok) {
return response.json().then(json => {throw new Error(json["error"])});
} else {
return response.json()
}
})
.then((data) => props.setData(data))
.catch((error) => props.messageApi.open({type: "error", content: error.toString()}));

};

return (
<div style={{display: 'flex', flexDirection: 'column'}}>
<DropdownMenu setOption={setMeasurement} selectedOptions={measurement} options={allMeasurements} single={true} name={"Measurements"}/>
<DropdownMenu setOption={setFields} selectedOptions={fields} options={allFields} name={"Fields"}/>
<DropdownMenu setOption={setFields} selectedOptions={fields} options={allFields} single={false} name={"Fields"}/>
<TimeStampPicker setStart={setStartEpoch} setEnd={setEndEpoch} />
<Button onClick={handleSubmit}>submit</Button>
</div>);
Expand Down
51 changes: 14 additions & 37 deletions software/tracksight/frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,44 @@
'use client';

import { useEffect, useState, React } from 'react';
import { io } from "socket.io-client";
import { useState } from 'react';
import { message, Button, Divider, Layout, Switch } from 'antd';
const { Header, Content } = Layout;

import styles from './page.module.css';
import NavBar from './components/navbar.tsx';
import Graph from './components/graph.tsx';
import Dashboard from './components/dashboard.tsx';
import NavBar from './components/navbar';
import Graph from './components/graph';
import Dashboard from './components/dashboard';
import { PlotRelayoutEvent } from 'plotly.js';

const FLASK_URL = "http://localhost:5000"
const FLASK_URL = "http://evanyl.pythonanywhere.com";

const Home = () => {
const [componentToDisplay, setComponentToDisplay] = useState("visualize");
const [socketInstance, setSocketInstance] = useState("");
const [loading, setLoading] = useState(true);
const [graphs, setGraphs] = useState([]);
const [zoomData, setZoomData] = useState([]);
//TODO: When we need to load stuff reimplement this
//const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [graphs, setGraphs] = useState<Array<string>>([]);
const [zoomData, setZoomData] = useState<PlotRelayoutEvent>({});
// determines if all graphs are supposed to zoom together or not
const [sync, setSync] = useState(false);
const [messageApi, contextHolder] = message.useMessage();

//add a new graph
const addGraph = () => {
const newGraphId = Date.now();
const newGraphId = Date.now().toString();
setGraphs(prevGraphs => [...prevGraphs, newGraphId]);
};

//delete a graph
const deleteGraph = (graphId) => {
const deleteGraph = (graphId: string) => {
setGraphs(prevGraphs => prevGraphs.filter(id => id !== graphId));
};

//set sync for all graphs
const setSyncAll = (sync) => {
const setSyncAll = (sync: boolean) => {
setSync(!sync);
}

useEffect(() => {
// NOTE -> mac users may need to turn airplay reciever off in order to connect to the server
const socket = io(FLASK_URL, {
transports: ["websocket"],
cors: {
origin: "http://localhost:3000/",
},
});

setSocketInstance(socket)
socket.on("connect", (data) => {
console.log(data);
setLoading(false);
});


socket.on("disconnect", (data) => {
console.log(data);
});
return () => {
socket.disconnect();
};
}, []);

let componentToRender;
if (componentToDisplay === "visualize") {
componentToRender = (
Expand Down
Loading

0 comments on commit ef4d7df

Please sign in to comment.