aboutsummaryrefslogtreecommitdiff
path: root/react-frontend/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'react-frontend/src/components')
-rw-r--r--react-frontend/src/components/DateSelector.js17
-rw-r--r--react-frontend/src/components/Hub.js24
-rw-r--r--react-frontend/src/components/HubList.js63
-rw-r--r--react-frontend/src/components/InvestorInfo.js66
-rw-r--r--react-frontend/src/components/Loading.js20
-rw-r--r--react-frontend/src/components/Modal.js70
-rw-r--r--react-frontend/src/components/TimeSelector.js48
-rw-r--r--react-frontend/src/components/Visualization.js103
-rw-r--r--react-frontend/src/components/WatchDogs.js89
-rw-r--r--react-frontend/src/components/images/logo.pngbin0 -> 43282 bytes
-rw-r--r--react-frontend/src/components/images/person.svg1
11 files changed, 501 insertions, 0 deletions
diff --git a/react-frontend/src/components/DateSelector.js b/react-frontend/src/components/DateSelector.js
new file mode 100644
index 0000000..bf01d44
--- /dev/null
+++ b/react-frontend/src/components/DateSelector.js
@@ -0,0 +1,17 @@
+// CSS import
+import '../css/Route.css';
+import '../css/CoordSelector.css';
+
+/**
+ * The component that selects and displays a coordinate.
+ * @param {Object} props The props for the element.
+ */
+function DateSelector(props) {
+ return (
+ <div className="Flex-coord">
+ <input type="date" value={props.value} onChange={(e) => props.changedFunc(new Date(e.target.value))} onClick={() => props.clickedFunc()}/>
+ </div>
+ );
+}
+
+export default DateSelector; \ No newline at end of file
diff --git a/react-frontend/src/components/Hub.js b/react-frontend/src/components/Hub.js
new file mode 100644
index 0000000..8a3ac1c
--- /dev/null
+++ b/react-frontend/src/components/Hub.js
@@ -0,0 +1,24 @@
+// React import
+import { useState } from "react";
+
+// CSS import
+import '../css/UserCheckin.css';
+
+/**
+ * Componenet for checkins. Has a toggle to show more info.
+ * @param {Object} props The props of the component.
+ * @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info.
+ */
+function Hub(props) {
+ // State - toggled
+
+ return (
+ <li className='Checkin'>
+ <div className="Img-flex">
+ <span className="Clickable-name" onClick= {() => console.log(props.id)}>{props.name}</span>
+ <span>{props.value.toFixed(3)}</span>
+ </div>
+ </li>);
+}
+
+export default Hub; \ No newline at end of file
diff --git a/react-frontend/src/components/HubList.js b/react-frontend/src/components/HubList.js
new file mode 100644
index 0000000..c9a5156
--- /dev/null
+++ b/react-frontend/src/components/HubList.js
@@ -0,0 +1,63 @@
+// React and component imports
+import { useEffect, useState } from "react";
+import Hub from "./Hub.js";
+import InvestorInfo from "./InvestorInfo.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([]);
+ const [isSelected, setIsSelected] = useState(false);
+ const [name, setName] = 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);
+ props.data.forEach(hub => hubs.push(
+ <Hub key={hub.id} id={hub.id} name={hub.name} value={hub.suspicionScore} setSelected={props.setSelected}></Hub>
+ ));
+
+ setHubItems(hubs);
+ }
+
+ const getName = () => {
+ props.data.forEach(hub => {
+ if (hub.id === props.selected) {
+ setName(hub.name);
+ }
+ })
+ setName('');
+ }
+
+
+ // React hook that updates when the hubs are recalculated
+ useEffect(() => updateHubItems(), [props.data]);
+
+ //React hook to show data for an investor
+ useEffect(() => {
+ setIsSelected(true)
+ getName();
+ }, [props.selected]);
+
+ return (
+ <div className="User-checkin">
+ <div className="Checkins">
+ <h2>Suspicion Ranks</h2>
+ <ul className='Checkin-list'>{hubItems}</ul>
+ </div>
+ <InvestorInfo personId={props.selected} isSelected={isSelected} name={name} dates={props.dates}></InvestorInfo>
+ </div>
+ );
+}
+
+export default HubList; \ No newline at end of file
diff --git a/react-frontend/src/components/InvestorInfo.js b/react-frontend/src/components/InvestorInfo.js
new file mode 100644
index 0000000..d368984
--- /dev/null
+++ b/react-frontend/src/components/InvestorInfo.js
@@ -0,0 +1,66 @@
+// React import
+import { useEffect, useState } from "react";
+
+// CSS import
+import '../css/UserCheckin.css';
+
+/**
+ * Componenet for checkins. Has a toggle to show more info.
+ * @param {Object} props The props of the component.
+ * @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info.
+ */
+function InvestorInfo(props) {
+ const [info, setInfo] = useState({});
+
+ const toEpochMilli = date => Date.parse(date);
+ const getInfo = () => {
+ console.log({
+ person: props.name,
+ start: toEpochMilli(props.dates.start),
+ end: toEpochMilli(props.dates.end)
+ });
+
+ if (props.name === "") {
+ return;
+ }
+
+ fetch("http://localhost:4567/profit", {
+ method: "POST",
+ body: JSON.stringify({
+ person: props.name,
+ start: toEpochMilli(props.dates.start),
+ end: toEpochMilli(props.dates.end)
+ }),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin"
+ })
+ .then(res => {
+ console.log(res);
+ res.json();
+ })
+ .then(data => {
+ console.log(data);
+ setInfo(data);
+ })
+ .catch(err => console.log(err));
+ }
+ /*
+
+ const coords = userCoords.map((coord, index) =>
+ <li key={index}>
+ <span>{'('+coord[0].toFixed(6)}, {coord[1].toFixed(6)+')'}</span>
+ </li>
+ );*/
+
+ useEffect(() => getInfo(), [props.name, props.isSelected, props.personId])
+
+ return (
+ <div className="Chosen-user" hidden={props.isSelected}>
+ hi
+ </div>
+ );
+}
+
+export default InvestorInfo; \ No newline at end of file
diff --git a/react-frontend/src/components/Loading.js b/react-frontend/src/components/Loading.js
new file mode 100644
index 0000000..6fdf5ba
--- /dev/null
+++ b/react-frontend/src/components/Loading.js
@@ -0,0 +1,20 @@
+// CSS import
+import '../css/App.css';
+
+/**
+ * Component that shows the program initially loading.
+ * @returns The loading widget - spinning logo :)
+ */
+function Loading() {
+ return (
+ <div className="App Loading">
+ <header className="App-header">
+ <img src={"./logo512.png"} className="App-logo" alt="logo" />
+ <h1 className="App-title">Loading WatchDogs</h1>
+ <p className="App-title">It takes a minute to connect to the servers and database :)</p>
+ </header>
+ </div>
+ );
+}
+
+export default Loading; \ No newline at end of file
diff --git a/react-frontend/src/components/Modal.js b/react-frontend/src/components/Modal.js
new file mode 100644
index 0000000..ad69650
--- /dev/null
+++ b/react-frontend/src/components/Modal.js
@@ -0,0 +1,70 @@
+import { useEffect, useState } from "react";
+
+import '../css/Modal.css';
+
+
+function Modal() {
+
+ const [count, setCount] = useState(0);
+
+ const nextModal1 = () => {
+ setCount(1);
+ }
+ const nextModal2 = () => {
+ setCount(2);
+ }
+ const nextModal3 = () => {
+ setCount(3);
+ }
+ const nextModal4 = () => {
+ setCount(4);
+ }
+ const startModal = () => {
+ document.getElementById("main-modal").style.display = 'block';
+ setCount(0);
+ }
+ const exitModal = () => {
+ document.getElementById("main-modal").style.display = 'none';
+ }
+
+
+ return (
+ <div>
+ <div id="main-modal"className="modal" style={{display : 'block'}}>
+ {(count == 0) && <p className="m modal0">
+ <p className="align-center"><span className="span">Welcome to WatchDogs!</span></p>
+ <p className="align-center">Click start for an introduction to the WatchDogs interface.</p>
+ <p className="align-center"><button className="next" onClick={nextModal1}>Start</button></p>
+ <p className="align-center"><button className="skip" onClick={exitModal}>Skip</button></p>
+ </p>}
+ {(count == 1) && <p className="m modal1">
+ <p className="align-right"><i class="arrow right"></i></p>
+ <span className="span">This is the suspicion ranking pane,</span> which displays
+ high-profile traders and the suspicion score our algorithm assigned
+ to them. The higher an individual is ranked, the more likely they are involved in insider trading.
+ <p className="align-center"><button className="next" onClick={nextModal2}>Next</button></p>
+ </p>}
+ {(count == 2) && <p className="m modal2">
+ <p></p>
+ <span className="span">The Timeframe pane </span>
+ allows you to chose the timespan you'd like to see trade data from. Individuals' suspicion score will
+ be calculated based on the trades that occured during the timeframe you select.
+ <p className="align-center"><button className="next" onClick={nextModal3}>Next</button></p>
+ <p className="align-right"><i class="arrow down"></i></p>
+ </p>}
+ {count == 3 && <p className="m modal3">
+ <p className="align-left"><i class="arrow left"></i></p>
+ <span className="span">The Trader Graph </span>
+ shows how traders are related. Click on a name in the graph to see more information about that individual.
+ <p className="align-center"><button className="next" onClick={nextModal4}>Next</button></p>
+ </p>}
+ {count == 4 && <p className="m modal4">
+ <button className="next" onClick={exitModal}>You're ready to start using WatchDogs!</button>
+ </p>}
+ </div>
+ <span className="restart-modal align-center" onClick={startModal}></span>
+ </div>
+ );
+}
+
+export default Modal; \ No newline at end of file
diff --git a/react-frontend/src/components/TimeSelector.js b/react-frontend/src/components/TimeSelector.js
new file mode 100644
index 0000000..652a9ec
--- /dev/null
+++ b/react-frontend/src/components/TimeSelector.js
@@ -0,0 +1,48 @@
+// React/Component imports
+import { useEffect, useState } from "react";
+import DateSelector from './DateSelector.js';
+
+// CSS imports
+import '../css/Route.css';
+
+
+/**
+ * The component that hold the forms for routing.
+ * @param {Object} props
+ */
+function TimeSelector(props) {
+ const [current, setCurrent] = useState("");
+
+ const toValue = date => new Date(date).toISOString().slice(0, 10);
+
+ const [startDate, setStartDate] = useState(props.dates.start);
+ const [endDate, setEndDate] = useState(props.dates.end);
+
+ const changeTimeframe = () => {
+ props.setDates({
+ start: startDate,
+ end: endDate
+ });
+ }
+
+ useEffect(() => setCurrent(""), [startDate, endDate]);
+
+ // The div with the html elements for routing.
+ return (
+ <div className="Route">
+ <div className="Coord-selectors-flex">
+ <DateSelector side={"left"} name={"Start Date"} className="Coord-select-left" clickedFunc={setCurrent}
+ changedFunc={setStartDate} disabled={current==='start' || props.isChanging} value={toValue(startDate)}></DateSelector>
+ <div>
+ <h2>Adjust Timeframe</h2>
+ <button className="Btn Route-btn" onClick={() => changeTimeframe()}
+ disabled={current!=="" || props.isChanging}>Change Timeframe</button>
+ </div>
+ <DateSelector side={"right"} name={"End Date"} className="Coord-select-right" clickedFunc={setCurrent}
+ changedFunc={setEndDate} disabled={current==='end' || props.isChanging} value={toValue(endDate)}></DateSelector>
+ </div>
+ </div>
+ );
+}
+
+export default TimeSelector; \ No newline at end of file
diff --git a/react-frontend/src/components/Visualization.js b/react-frontend/src/components/Visualization.js
new file mode 100644
index 0000000..0a0c82a
--- /dev/null
+++ b/react-frontend/src/components/Visualization.js
@@ -0,0 +1,103 @@
+// JS module imports
+import { useEffect, useRef, useState } from "react";
+import uuid from 'react-uuid';
+import Graph from 'vis-react';
+
+// CSS imports
+import '../css/Canvas.css';
+
+
+/**
+ * This function renders and mantains thhe canvas.
+ * @param {Object} props The props for the canvas.
+ * @returns {import("react").HtmlHTMLAttributes} The canvas to be retured.
+ */
+function Visualization(props) {
+ const options = {
+ edges: {
+ color: "#ffffff"
+ }
+ };
+ const events = {
+ select: () => event => props.setSelected(event.nodes[0])
+ };
+
+ const [graphState, setGraphState] = useState({
+ nodes: [],
+ edges: []
+ });
+ const getNodes = () => {
+ let nodes = [];
+ props.data.forEach(hub => {
+ if (hub.followers) {
+ let colorVal = '#f6f7d4';
+ const score = hub.suspicionScore;
+
+ if(score > 0.8){
+ colorVal = '#d92027'
+ }
+ if(score < 0.8 && score > 0.6){
+ colorVal = '#f37121'
+ }
+ if(score < 0.6 && score > 0.4){
+ colorVal = '#fdca40'
+ }
+ nodes.push({
+ id: hub.id,
+ autoResize: true,
+ label: hub.name,
+ labelHighlightBold: true,
+ shape: "dot",
+ value: hub.suspicionScore*1000,
+ color: {
+ background: colorVal,
+ border: '#2b2e4a',
+ highlight:{
+ background: '#29bb89',
+ border: '#fdca40'
+ }
+ },
+ font: {
+ color: '#9fd8df',
+ size: 20,
+ }
+ });
+ }
+ });
+ return nodes;
+ }
+ const getEdges = () => {
+ let edges = []
+ props.data.forEach(hub => {
+ hub.followers.forEach(follower => {
+ edges.push({
+ from: follower.id,
+ to: hub.id,
+ dashes: false,
+ color:{
+ opacity: 0.7,
+ highlight:'#fdca40',
+ }
+ });
+ });
+ });
+ return edges;
+ }
+
+ // Hooks to update graph state
+ useEffect(() => setGraphState({nodes: getNodes(), edges: getEdges()}), [JSON.stringify(props.data)]);
+
+ return (
+ <div className="Map-canvas">
+ <Graph
+ key={uuid()}
+ graph={graphState}
+ options={options}
+ events={events}>
+ </Graph>
+ </div>
+ );
+}
+
+export default Visualization;
+
diff --git a/react-frontend/src/components/WatchDogs.js b/react-frontend/src/components/WatchDogs.js
new file mode 100644
index 0000000..d631ea9
--- /dev/null
+++ b/react-frontend/src/components/WatchDogs.js
@@ -0,0 +1,89 @@
+// React/component imports
+import React, {useEffect, useState} from 'react';
+import TimeSelector from './TimeSelector.js';
+import Visualization from './Visualization.js';
+import HubList from './HubList.js';
+import Loading from './Loading.js';
+import Modal from './Modal.js';
+import logo from './images/logo.png';
+
+// CSS import
+import '../css/App.css';
+
+/**
+ * Main component of the app. Holds the main layout of the big components.
+ * @returns {import('react').HtmlHTMLAttributes} A div of the body of the page.
+ */
+function WatchDogs() {
+ // State to open app when loaded
+ const [hasLoaded, setHasLoaded] = useState(false);
+ // State indicate if canvas is redrawing
+ const [isChanging, setIsChanging] = useState(false);
+ // State to hold dates -> two weeks apart on initialization.
+ const [dates, setDates] = useState({
+ start: new Date(Date.now() - 12096e5),
+ end: new Date()
+ });
+ // State for visualization data
+ const [data, setData] = useState([]);
+ // State for selected person
+ const [selected, setSelected] = useState(-1);
+
+ const toEpochMilli = date => Date.parse(date);
+ const getGraphData = () => {
+ console.log({
+ start: toEpochMilli(dates.start),
+ end: toEpochMilli(dates.end)
+ });
+ fetch("http://localhost:4567/data", {
+ method: "POST",
+ body: JSON.stringify({
+ start: toEpochMilli(dates.start),
+ end: toEpochMilli(dates.end)
+ }),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin"
+ })
+ .then(res => res.json())
+ .then(data => {
+ //TODO: optimize this
+ const sliced = data.holders.slice(0, 500);
+ console.log(sliced);
+ setData(sliced);
+ setHasLoaded(true);
+ })
+ .catch(err => console.log(err));
+
+ setIsChanging(false);
+ }
+
+
+ // Hooks to update data on init and switching of data
+ //useEffect(() => getGraphData(), []);
+ useEffect(() => {
+ setIsChanging(true);
+ getGraphData();
+ }, [dates]);
+
+ return (
+ <>
+ {(!hasLoaded) ? <Loading></Loading> :
+ <div className="App">
+ <Modal>PHP</Modal>
+ <header id="in-app-logo-holder" className="App-header">
+ <img id="in-app-logo" src={logo} alt="logo"/></header>
+ <div className="Canvas-filler Canvas-filler-1"></div>
+ <div className="Canvas-filler Canvas-filler-2"></div>
+ <div className="Canvas-filler Canvas-filler-3"></div>
+ <HubList setHasLoaded={setHasLoaded} data={data} setSelected={setSelected} selected={selected} dates={dates}></HubList>
+ <TimeSelector isChanging={isChanging} dates={dates} setDates={setDates}></TimeSelector>
+ <Visualization hasLoaded={hasLoaded} data={data} setSelected={setSelected}></Visualization>
+ </div>
+ }
+ </>
+ );
+}
+
+export default WatchDogs;
diff --git a/react-frontend/src/components/images/logo.png b/react-frontend/src/components/images/logo.png
new file mode 100644
index 0000000..7e4e9ee
--- /dev/null
+++ b/react-frontend/src/components/images/logo.png
Binary files differ
diff --git a/react-frontend/src/components/images/person.svg b/react-frontend/src/components/images/person.svg
new file mode 100644
index 0000000..6a93d8f
--- /dev/null
+++ b/react-frontend/src/components/images/person.svg
@@ -0,0 +1 @@
+<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Person</title><path d='M332.64 64.58C313.18 43.57 286 32 256 32c-30.16 0-57.43 11.5-76.8 32.38-19.58 21.11-29.12 49.8-26.88 80.78C156.76 206.28 203.27 256 256 256s99.16-49.71 103.67-110.82c2.27-30.7-7.33-59.33-27.03-80.6zM432 480H80a31 31 0 01-24.2-11.13c-6.5-7.77-9.12-18.38-7.18-29.11C57.06 392.94 83.4 353.61 124.8 326c36.78-24.51 83.37-38 131.2-38s94.42 13.5 131.2 38c41.4 27.6 67.74 66.93 76.18 113.75 1.94 10.73-.68 21.34-7.18 29.11A31 31 0 01432 480z'/></svg> \ No newline at end of file