aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Foiani <sotech117@michaels-mbp-3.lan>2021-04-16 15:34:25 -0400
committerMichael Foiani <sotech117@michaels-mbp-3.lan>2021-04-16 15:34:25 -0400
commit579fa51b8b306fb201c799d33e633e58819463fb (patch)
treec2c56da25fc3e43836451df6d78f94d120ab75f1
parentf00ac29dff86169e5dee9d816961cc13979f9a50 (diff)
Basic way to show the hubs with their scores.
-rw-r--r--maps-frontend/src/App.js11
-rw-r--r--maps-frontend/src/components/Canvas.js557
-rw-r--r--maps-frontend/src/components/CheckinList.js129
-rw-r--r--maps-frontend/src/components/Hub.js (renamed from maps-frontend/src/components/UserCheckin.js)10
-rw-r--r--maps-frontend/src/components/HubList.js44
-rw-r--r--maps-frontend/src/components/TimeSelector.js (renamed from maps-frontend/src/components/Route.js)0
6 files changed, 55 insertions, 696 deletions
diff --git a/maps-frontend/src/App.js b/maps-frontend/src/App.js
index 0e25f39..659b8f5 100644
--- a/maps-frontend/src/App.js
+++ b/maps-frontend/src/App.js
@@ -2,7 +2,7 @@
import React, {useEffect, useState} from 'react';
import TimeSelector from './components/TimeSelector.js';
import Canvas from './components/Canvas.js';
-import DataWidget from './components/DataWidget.js';
+import HubList from './components/HubList.js';
import Loading from './components/Loading.js';
// CSS import
@@ -23,7 +23,7 @@ function App() {
end: new Date(Date.now() - 12096e5)
});
// State for visualization data
- const [graphData, setGraphData] = useState({});
+ const [data, setData] = useState([]);
// States to control cursor.
const [cursor, setCursor] = useState('grab');
// State for routing.
@@ -45,7 +45,7 @@ function App() {
})
.then(res => res.json())
.then(data => {
- setGraphData(data);
+ setData(data);
setHasLoaded(true);
})
.catch(err => console.log(err));
@@ -75,9 +75,8 @@ function App() {
<div className="Canvas-filler Canvas-filler-1"></div>
<div className="Canvas-filler Canvas-filler-2"></div>
<div className="Canvas-filler Canvas-filler-3"></div>
- <DataWidget setHasLoaded={setHasLoaded}></DataWidget>
- <Canvas setCoord={setCoord} route={route} selector={currentSelector} startLat={startLat}
- startLon={startLon} endLat={endLat} endLon={endLon} setCursor={setCursor}
+ <HubList setHasLoaded={setHasLoaded} data={data}></HubList>
+ <Canvas setCursor={setCursor}
hasLoaded={hasLoaded} setHasLoaded={setHasLoaded}></Canvas>
{(!hasLoaded) ? <Loading></Loading> :
<TimeSelector isChanging={isChanging} dates={dates} setDates={setDates}></TimeSelector>}
diff --git a/maps-frontend/src/components/Canvas.js b/maps-frontend/src/components/Canvas.js
index 166967e..9686f29 100644
--- a/maps-frontend/src/components/Canvas.js
+++ b/maps-frontend/src/components/Canvas.js
@@ -1,6 +1,5 @@
// JS module imports
import { useEffect, useRef, useState } from "react";
-import axios from 'axios';
// CSS imports
import '../css/Canvas.css';
@@ -10,562 +9,12 @@ import '../css/Canvas.css';
* @param {Object} props The props for the canvas.
* @returns {import("react").HtmlHTMLAttributes} The canvas to be retured.
*/
-function Canvas(props) {
+function Visualization(props) {
// instance variables
- const CANVAS_WIDTH = window.innerWidth;
- const CANVAS_HEIGHT = window.innerHeight;
-
- const SCALE_RATIO = .035/.015;
- const START_LON_SCALE = .005;
- const MIN_LON_SCALE = .001;
- const MAX_LON_SCALE = 1;
- const SCALE_INTERVAL = .0007;
- const TILE_SIZE = .1;
- // Ways processing. Long object...
- const A_TIER_MAX_SCALE = .175;
- const B_TIER_MAX_SCALE = .08;
- const WAYS_INFO = {
- primary: {
- color: 'darkorange',
- weight: 2,
- maxScale: MAX_LON_SCALE
- },
- motorway: {
- color: 'darkorange',
- weight: 1.85,
- maxScale: MAX_LON_SCALE
- },
- secondary: {
- color: 'orange',
- weight: 1.75,
- maxScale: MAX_LON_SCALE
- },
- tertiary: {
- color: 'orange',
- weight: 1.75,
- maxScale: MAX_LON_SCALE
- },
- residential: {
- color: 'white',
- weight: 1.75,
- maxScale: MAX_LON_SCALE
- },
- primary_link: {
- color: 'yellow',
- weight: 1.6,
- maxScale: A_TIER_MAX_SCALE
- },
- motorway_link: {
- color: 'yellow',
- weight: 1.5,
- maxScale: A_TIER_MAX_SCALE
- },
- secondary_link: {
- color: 'yellow',
- weight: 1.5,
- maxScale: A_TIER_MAX_SCALE
- },
- tertiary_link: {
- color: 'yellow',
- weight: 1.3,
- maxScale: A_TIER_MAX_SCALE
- },
- footway: {
- color: 'mediumorchid',
- weight: 1.25,
- maxScale: .25
- },
- cycleway: {
- color: 'blue',
- weight: 1.35,
- maxScale: A_TIER_MAX_SCALE
- },
- trunk: {
- color: 'amber',
- weight: 1.25,
- maxScale: A_TIER_MAX_SCALE
- },
- trunk_link: {
- color: 'amber',
- weight: 1,
- maxScale: A_TIER_MAX_SCALE
- },
- road: {
- color: 'white',
- weight: 1.25,
- maxScale: A_TIER_MAX_SCALE
- },
- living_street: {
- color: 'white',
- weight: 1.25,
- maxScale: A_TIER_MAX_SCALE
- },
-
- service: {
- color: 'red',
- weight: 1,
- maxScale: B_TIER_MAX_SCALE
- },
- construction: {
- color: 'red',
- weight: 1.25,
- maxScale: B_TIER_MAX_SCALE
- },
- pedestrian: {
- color: 'purple',
- weight: 1,
- maxScale: B_TIER_MAX_SCALE
- },
- track: {
- color: 'pink',
- weight: 1.2,
- maxScale: B_TIER_MAX_SCALE
- },
- steps: {
- color: 'purple',
- weight: 1.1,
- maxScale: B_TIER_MAX_SCALE
- },
- path: {
- color: 'purple',
- weight: 1.1,
- maxScale: B_TIER_MAX_SCALE
- }
- }
-
- const START_COORD = {
- lat: 41.825,
- lon: -71.406
- }
-
- // React ref to canvas
- const ctxRef = useRef();
-
- //Reach states
- const [canvasWidth, setCanvasWidth] = useState(window.innerWidth);
- const [canvasHeight, setCanvasHeight] = useState(window.innerHeight);
- const [dragStart, setDragStart] = useState({});
- const [scale, setScale] = useState(START_LON_SCALE);
- const [referencePoint, setReferencePoint] = useState(START_COORD);
- const [tiles, setTiles] = useState({});
- const [requests, setRequests] = useState(new Set());
- const [mouseDown, setMouseDown] = useState(false);
- const [canvasImg, setCanvasImg] = useState();
-
- // The following four methods map the latitude, longitude to the x,y of the grid.
-
- /**
- * The method mapping x to lon.
- * @param {Number} x The x pixel difference to be converted.
- * @returns {Number} The the corresponding longitude .
- */
- const xToLon = x => {
- return x*(scale/canvasWidth);
- }
-
- /**
- * The method mapping y to lat.
- * @param {Number} y The y pixel difference to be converted.
- * @returns {Number} The the corresponding latitude.
- * It's reversed due to origin being at top left (not bottom left).
- */
- const yToLat = y => {
- const latScale = scale/SCALE_RATIO;
- return -y*(latScale/canvasHeight);
- }
-
- /**
- * The method mapping lon to x.
- * @param {Number} lon The longitude to be coverted to x pixel difference.
- * @returns {Number} The the corresponding x pixel difference.
- */
- const lonToX = lon => {
- return lon/(scale/canvasWidth);
- }
-
-
- /**
- * @param {Number} lat The latitude to be converted to y pixel difference.
- * @returns {Number} The the corresponding y value.
- * It's reversed due to the difference origins.
- */
- const latToY = lat => {
- const latScale = scale/SCALE_RATIO;
- return -lat/(latScale/canvasHeight);
- }
-
- /**
- * Calculated the current grid's corners to use in the ways command.
- * @returns {Object} The top left and bottom right coordinates in terms of the lat and lon.
- */
- const getLatLonBounds = () => {
- const lat1 = referencePoint.lat + TILE_SIZE;
- const long1 = referencePoint.lon - TILE_SIZE;
- const lat2 = referencePoint.lat + yToLat(canvasHeight) - TILE_SIZE;
- const long2 = referencePoint.lon + xToLon(canvasWidth) + TILE_SIZE;
- const topLeft = {
- lat1: +(Math.round((lat1 * 1000)/10)/100).toFixed(1),
- long1: +(Math.round((long1 * 1000)/10)/100).toFixed(1)
- }
- const botRight = {
- lat2: +(Math.round((lat2 * 1000)/10)/100).toFixed(1),
- long2: +(Math.round((long2 * 1000)/10)/100).toFixed(1)
- }
-
- return {...topLeft, ...botRight};
- }
-
- /**
- * Method the gets a tile and puts it into the cache.
- * @param {Object} topLeft The topleft coordinate of the tile.
- * @returns The ways within the tile.
- */
- const getTile = async topLeft => {
- // TODO: implement cache
-
- const toSend = {
- lat1: topLeft[0],
- long1: topLeft[1],
- lat2: +(topLeft[0] - TILE_SIZE).toFixed(1),
- long2: +(topLeft[1] + TILE_SIZE).toFixed(1)
- }
- //console.log(toSend);
-
- let config = {
- headers: {
- "Content-Type": "application/json",
- 'Access-Control-Allow-Origin': '*',
- }
- };
-
- //Install and import this!
- //TODO: Fill in 1) location for request 2) your data 3) configuration
- const res = await axios.post(
- "http://localhost:4567/maps/ways",
- JSON.stringify(toSend),
- config
- );
-
- setTiles(prevState => ({
- ...prevState,
- [`lat${topLeft[0]}lon${topLeft[1]}`] : res.data["ways"]
- }));
-
- return res.data["ways"];
- };
-
- /**
- * Method that draws a single way onto the canvas.
- * @param {import("react").HtmlHTMLAttributes} ctx The context of the canvas.
- * @param {Object} wayInfo The information of the way as an object.
- */
- const drawWay = (ctx, wayInfo) => {
- // TODO: implement cache
- ctx.beginPath();
-
- const startY = latToY(wayInfo.startLat - referencePoint.lat);
- const startX = lonToX(wayInfo.startLong - referencePoint.lon);
- ctx.moveTo(startX, startY);
-
- const endY = latToY(wayInfo.destLat - referencePoint.lat);
- const endX = lonToX(wayInfo.destLong - referencePoint.lon);
- ctx.lineTo(endX, endY);
-
- if (wayInfo.type && WAYS_INFO[wayInfo.type]) {
- if (WAYS_INFO[wayInfo.type].maxScale >= scale) {
- ctx.strokeStyle = WAYS_INFO[wayInfo.type].color;
- ctx.lineWidth = WAYS_INFO[wayInfo.type].weight;
- ctx.stroke();
- }
- } else {
- if (B_TIER_MAX_SCALE >= scale) {
- ctx.strokeStyle = 'grey';
- ctx.lineWidth = 1;
- ctx.stroke();
- }
- }
-
- if (props.route.includes(wayInfo.id)) {
- ctx.strokeStyle = 'lightgreen';
- ctx.lineWidth = 10;
- ctx.stroke();
- }
- }
-
- /**
- * Handles the timeout from multiple quick requests to the api to ease the server.
- * @param {CallableFunction} func The pending function call.
- * @param {Number} delay The time in milliseconds to delay.
- * @returns {CallableFunction} The function that runs the code after the delay.
- */
- const debounce = (func, delay) => {
- let debounceTimer
- return () => {
- const context = this
- const args = arguments
- clearTimeout(debounceTimer)
- debounceTimer = setTimeout(() => func.apply(context, args), delay)
- }
- }
-
- /**
- * Determines whether the a tile is loaded on the canvas.
- * @param {Object} topLeft The topLeft point of the tile.
- * @returns True if any section of it is loaded on the canvas.
- */
- const tileOnScreen = topLeft => {
- const tileLatBounds = [+(topLeft[0] - TILE_SIZE).toFixed(1), topLeft[0]];
- const tileLongBounds = [topLeft[1], +(topLeft[1] + TILE_SIZE).toFixed(1)];
- const screenLatBounds = [referencePoint.lat, referencePoint.lat + yToLat(canvasHeight)]
- const screenLongBounds = [referencePoint.lon, referencePoint.lon + xToLon(canvasWidth)]
- //by checking if the following for things are true, we can determine if the rectangles intersect
- const tileLeftScreen = tileLatBounds[1] < screenLatBounds[1];
- const tileRightScreen = tileLatBounds[0] > screenLatBounds[0];
- const tileAboveScreen = tileLongBounds[0] > screenLongBounds[1];
- const tileBelowScreen = tileLongBounds[1] < screenLongBounds[0];
- return !( tileLeftScreen || tileRightScreen || tileAboveScreen || tileBelowScreen );
- }
-
- /**
- * Method used to update the ways on the canvas.
- * It draws in the new tiles and translates the old.
- */
- const updateWaysOnCanvas = debounce(() => {
- const ctx = ctxRef.current.getContext("2d");
- const bounds = getLatLonBounds();
- ctx.canvas.width = canvasWidth;
- ctx.fillStyle = "#121212";
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
- for (let i = +(bounds.lat2 + TILE_SIZE).toFixed(1); i <= bounds.lat1; i = +(i + TILE_SIZE).toFixed(1)) {
- for (let j = bounds.long1; j < bounds.long2; j = +(j + TILE_SIZE).toFixed(1)) {
- const topLeft = [i, j];
- const pointId = `lat${i}lon${j}`;
- if (tiles[pointId] && tileOnScreen(topLeft)) {
- //('accessing cache!');
- Object.keys(tiles[pointId]).forEach(key => {
- drawWay(ctx, tiles[pointId][key]);
- });
- } else {
- if (!requests.has(pointId)) {
- setRequests(requests.add(pointId));
- getTile(topLeft).then((data) => {
- if (tileOnScreen(topLeft)) {
- Object.keys(data).forEach(key => {
- drawWay(ctx, data[key]);
- });
- if (!props.hasLoaded) {
- props.setHasLoaded(true);
- }
- }
- });
- }
- }
- }
- }
-
- // draws the routing points if they have been set
- drawPoints();
- }, 0);
-
- /**
- * Stores the starting point of the drah.
- * @param {Object} e The event from the mouseDown callback.
- */
- const storeDragStart = e => {
- setMouseDown(true);
- const canvas = ctxRef.current;
- setCanvasImg(canvas);
-
- setDragStart({
- x: e.pageX - canvas.offsetLeft,
- y: e.pageY - canvas.offsetTop
- });
-
- props.setCursor('grabbing');
- }
-
- /**
- * Makes a call to the server to determine the nearest neighbor.
- * @param {Number} x The x pixel value of the point inputted.
- * @param {Number} y The y pixel value of the point inputted.
- * @returns {Object} The lat and lon coordinates of the nearest.
- */
- const getNearest = async (x, y) => {
- const toSend = {
- lat: yToLat(y) + referencePoint.lat,
- long: xToLon(x) + referencePoint.lon
- };
-
- //console.log(toSend);
-
- let config = {
- headers: {
- "Content-Type": "application/json",
- 'Access-Control-Allow-Origin': '*',
- }
- };
-
- //Install and import this!
- //TODO: Fill in 1) location for request 2) your data 3) configuration
- const res = await axios.post(
- "http://localhost:4567/maps/nearest",
- JSON.stringify(toSend),
- config
- )
-
- return res.data;
- }
-
- /**
- * This method draws the start and end points for the routing.
- */
- const drawPoints = () => {
- const ctx = ctxRef.current.getContext('2d');
-
- const startY = latToY(props.startLat - referencePoint.lat);
- const startX = lonToX(props.startLon - referencePoint.lon);
-
- const endY = latToY(props.endLat - referencePoint.lat);
- const endX = lonToX(props.endLon - referencePoint.lon);
-
- const drawStart = () => {
- ctx.beginPath();
- ctx.arc(startX, startY, 15, 0, Math.PI * 2, true);
-
- ctx.strokeStyle = 'pink';
- ctx.lineWidth = 10;
- ctx.stroke();
- }
-
- const drawEnd = () => {
- ctx.beginPath();
- ctx.arc(endX, endY, 15, 0, Math.PI * 2, true);
-
- ctx.strokeStyle = 'lightblue';
- ctx.lineWidth = 10;
- ctx.stroke();
- }
-
- const startNotChosen = props.startLat === 0 && props.startLon === 0;
- const endNotChosen = props.endLat === 0 && props.endLon === 0;
-
- if (!startNotChosen && !endNotChosen) {
- drawStart();
- drawEnd();
- } else if (!startNotChosen) {
- drawStart();
- } else if (!endNotChosen) {
- drawEnd();
- }
- }
-
- /**
- * Drags the canvas and also responds to a solo click for inputting the route coordinate.
- * @param {Object} e The event from the callback click handler.
- */
- const dragCanvas = e => {
- const canvas = ctxRef.current;
-
- const endX = e.pageX - canvas.offsetLeft;
- const endY = e.pageY - canvas.offsetTop;
-
- const distX = endX - dragStart.x;
- const distY = endY - dragStart.y;
-
- setDragStart({
- x: e.pageX - canvas.offsetLeft,
- y: e.pageY - canvas.offsetTop
- });
-
- if ((distX === 0 || distY === 0) && props.selector !== '') {
- getNearest(endX, endY).then((data) => {
- props.setCoord(data);
- });
- } else {
- setReferencePoint((refPoint) => {
- return {
- lat: refPoint.lat - yToLat(distY),
- lon: refPoint.lon - xToLon(distX)
- }
- });
- }
- }
-
- /**
- * Makes dragging smmother by calling the drag command frame by frame.
- * @param {Object} e The event from the callback mouseMove hanlder.
- */
- const smoothDrag = e => {
- if (mouseDown) {
- requestAnimationFrame(() => dragCanvas(e));
- props.setCursor('grabbing');
- }
- }
-
- /**
- * Releases the drag by setting the cursor back and setting the state.
- * @param {Object} e The event from the callback mouseDown handler.
- */
- const releaseDrag = e => {
- props.setCursor('grab');
- setMouseDown(false);
- }
-
- // The following are the react hooks that act as handlers and rerender on change.
-
- useEffect(() => {
- updateWaysOnCanvas();
- }, [referencePoint, props.route]);
-
- useEffect(() => {
- updateWaysOnCanvas();
- }, [props.selector]);
-
- useEffect(() => {
- const timer = setTimeout(() => {
- updateWaysOnCanvas();
- props.setCursor('grab');
- }, 750);
- return () => clearTimeout(timer);
- }, [scale]);
-
- useEffect(() => {
- const canvas = ctxRef.current;
-
- canvas.width = canvasWidth;
- canvas.height = canvasHeight;
-
- const zoomCanvas = e => {
- e.preventDefault();
-
- let direction = (e.deltaY > 0) ? 1 : -1;
- props.setCursor(direction === 1 ? 'zoom-out' : 'zoom-in');
- setScale((s) => {
- const newScale = s + direction*SCALE_INTERVAL;
- return Math.min(MAX_LON_SCALE, Math.max(newScale, MIN_LON_SCALE));
- });
- }
- canvas.addEventListener('wheel', zoomCanvas, false);
-
- window.addEventListener('resize', () => {
- setCanvasWidth(window.innerWidth);
- setCanvasHeight(window.innerHeight);
- });
- }, []);
-
- useEffect(() => {
- const canvas = ctxRef.current;
- canvas.width = canvasWidth;
- canvas.height = canvasHeight;
- updateWaysOnCanvas();
- }, [canvasWidth, canvasHeight])
-
-
- return <canvas ref={ctxRef} onMouseDown={(e) => storeDragStart(e)} onClick={(e) => dragCanvas(e)}
- onMouseMove={(e) => smoothDrag(e)} onMouseUp={() => releaseDrag()} className="Map-canvas"></canvas>;
+ return
}
-export default Canvas;
+export default Visualization;
diff --git a/maps-frontend/src/components/CheckinList.js b/maps-frontend/src/components/CheckinList.js
deleted file mode 100644
index 189ed8b..0000000
--- a/maps-frontend/src/components/CheckinList.js
+++ /dev/null
@@ -1,129 +0,0 @@
-// React and component imports
-import axios from 'axios';
-import { useEffect, useState } from "react";
-import UserCheckin from './UserCheckin.js';
-
-// CSS import
-import '../css/UserCheckin.css';
-
-/**
- * Component that build the checkin list and displays checkin info.
- * @returns {import('react').HtmlHTMLAttributes} A div with the checkins
- * in a vertical layout.
- */
-function CheckinList() {
- // States...
- const [checkins, setCheckins] = useState({});
- const [checkinItems, setCheckinItems] = useState([]);
- const [showAllCheckins, setShowAllCheckins] = useState(true);
- const [userInfo, setUserInfo] = useState([]);
- const [userCoords, setUserCoords] = useState([]);
-
- /**
- * Makes a call to the server to get the newer checkin data.
- */
- function getNewCheckins() {
- let config = {
- headers: {
- "Content-Type": "application/json",
- 'Access-Control-Allow-Origin': '*',
- }
- }
-
- axios.get(
- "http://localhost:4567/maps/checkins",
- config
- ).then((res) => {
- setCheckins((prev) => {
- return Object.assign(prev, res.data['checkins']);
- });
- });
- updateCheckinItems();
- }
-
- /**
- * Gives all the checkins for a specific user.
- * @param {String} id The user id to get the checkins for.
- * @param {String} name The name to get the checkins for.
- */
- function getUserCheckins(id, name) {
- const toSend = {
- userid : id,
- };
- let config = {
- headers: {
- "Content-Type": "application/json",
- 'Access-Control-Allow-Origin': '*',
- }
- }
-
- axios.post(
- "http://localhost:4567/maps/checkins",
- JSON.stringify(toSend),
- config
- )
- .then(res => {
- setUserCoords(res.data['checkins']);
- })
- setUserInfo([name, id]);
- setShowAllCheckins(false);
- }
-
- /**
- * Generates the user checkins html elements.
- * @returns A div holding a list of all checkins for a user.
- */
- function getUserCheckinElements() {
- const coords = userCoords.map((coord, index) =>
- <li key={index}>
- <span>{'('+coord[0].toFixed(6)}, {coord[1].toFixed(6)+')'}</span>
- </li>
- );
- return (
- <div className="Chosen-user" hidden={showAllCheckins}>
- <h2>
- <span onClick={() => setShowAllCheckins(true)}><img className="Img-btn" src="/round_arrow_back_white_18dp.png" alt="image"/></span>
- {userInfo[0]} : {userInfo[1]}
- </h2>
- <div className='Coord-ex'>{"(lat , lon)"}</div>
- <ol className='User-checkin-list'>
- {coords}
- </ol>
- </div>
- );
- }
-
- /**
- * Loads new the checkins into the current cache/map of checkins.
- */
- const updateCheckinItems = () => {
- let tempCheckinItems = [];
- const sortedCheckinEntries = Object.entries(checkins).sort((a, b) => b[0] - a[0]);
- for (const [key, value] of sortedCheckinEntries) {
- tempCheckinItems.push(
- <UserCheckin key={key} value={value} getUserCheckins={getUserCheckins}></UserCheckin>
- );
- }
- setCheckinItems(tempCheckinItems);
- }
-
- // React hook that queries the checkin database every 5 seconds.
- useEffect(() => {
- const interval = setInterval(() => {
- getNewCheckins();
- }, 5000);
- return () => clearInterval(interval);
- }, []);
-
- return (
- <div className="User-checkin">
- <div className="Checkins">
- <h2>Checkins</h2>
- <ul className='Checkin-list'>{checkinItems}</ul>
- </div>
- {getUserCheckinElements()}
- </div>
- );
-}
-
-export default CheckinList; \ No newline at end of file
diff --git a/maps-frontend/src/components/UserCheckin.js b/maps-frontend/src/components/Hub.js
index f85994b..cd160ea 100644
--- a/maps-frontend/src/components/UserCheckin.js
+++ b/maps-frontend/src/components/Hub.js
@@ -9,7 +9,7 @@ import '../css/UserCheckin.css';
* @param {Object} props The props of the component.
* @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info.
*/
-function UserCheckin(props) {
+function Hub(props) {
// State - toggled
const [isToggled, setIsToggled] = useState(false);
@@ -21,13 +21,9 @@ function UserCheckin(props) {
<img className="Img-btn" hidden={!isToggled} onClick={() => setIsToggled((toggle) => !toggle)} src="/round_expand_less_white_18dp.png" alt="image"/>
</div>
<div hidden={!isToggled}>
- <ul>
- <li>Time: {new Date(props.value.ts * 1000).toLocaleTimeString("en-US")}</li>
- <li>Lat: {props.value.lat}</li>
- <li>Lon: {props.value.lon}</li>
- </ul>
+ Nothin....
</div>
</li>);
}
-export default UserCheckin; \ No newline at end of file
+export default Hub; \ No newline at end of file
diff --git a/maps-frontend/src/components/HubList.js b/maps-frontend/src/components/HubList.js
new file mode 100644
index 0000000..3bbcca6
--- /dev/null
+++ b/maps-frontend/src/components/HubList.js
@@ -0,0 +1,44 @@
+// React and component imports
+import { useEffect, useState } from "react";
+import Hub from "./Hub.js";
+
+// CSS import
+import '../css/UserCheckin.css';
+
+/**
+ * Component that build the checkin list and displays checkin info.
+ * @returns {import('react').HtmlHTMLAttributes} A div with the hubs
+ * in a vertical layout.
+ */
+function HubList(props) {
+ const [hubItems, setHubItems] = useState([]);
+
+ /**
+ * Loads new the checkins into the current cache/map of hubs.
+ */
+ const updateHubItems = () => {
+ // sort and create the elemnts
+ let hubs = [];
+ const sorted = props.data.sort((a, b) => b.suspicionScore - a.suspicionScore);
+ console.log(sorted);
+ sorted.forEach(hub => hubs.push(
+ <Hub name={hub.name} value={hub.suspicionScore}></Hub>
+ ));
+
+ setHubItems(hubs);
+ }
+
+ // React hook that updates when the hubs are recalculated
+ useEffect(() => updateHubItems(), [props.data]);
+
+ return (
+ <div className="User-checkin">
+ <div className="Checkins">
+ <h2>Individual Suspicion</h2>
+ <ul className='Checkin-list'>{hubItems}</ul>
+ </div>
+ </div>
+ );
+}
+
+export default HubList \ No newline at end of file
diff --git a/maps-frontend/src/components/Route.js b/maps-frontend/src/components/TimeSelector.js
index 2a26fd9..2a26fd9 100644
--- a/maps-frontend/src/components/Route.js
+++ b/maps-frontend/src/components/TimeSelector.js