);
58 | expect(wrapper.type()).toEqual('div');
59 | expect(wrapper.find('.gallery'));
60 | });
61 |
62 | // it('Routes to different gallery views based on endpoint', () => {
63 | // wrapper = shallow(
68 |
69 | // expect(wrapper.find(BrokerDetails)).toHaveLength(1);
70 | // });
71 | });
72 |
73 | describe('BrokerDetails', () => {
74 | let wrapper = shallow(
tag with id broker-wrapper', () => {
77 | expect(wrapper.type()).toEqual('div');
78 | expect(wrapper.find('#broker-wrapper'));
79 | });
80 | })
81 |
82 | });
--------------------------------------------------------------------------------
/__tests__/supertest.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/deKaf/1658de7054650a5d22b1f383b7e6d06154944e23/__tests__/supertest.ts
--------------------------------------------------------------------------------
/__tests__/tests.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/deKaf/1658de7054650a5d22b1f383b7e6d06154944e23/__tests__/tests.ts
--------------------------------------------------------------------------------
/docker-compose-dev-hot.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | dev:
4 | image: "teamdekaf/dekaf-dev"
5 | container_name: "dekaf-dev-hot"
6 | ports:
7 | - "8080:8080"
8 | volumes:
9 | - .:/usr/src/app
10 | - node_modules:/usr/src/app/node_modules
11 | command: npm run dev
12 | volumes:
13 | node_modules:
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | zookeeper:
4 | image: 'bitnami/zookeeper:latest'
5 | ports:
6 | - '2181:2181'
7 | environment:
8 | - ALLOW_ANONYMOUS_LOGIN=yes
9 | kafka:
10 | image: 'bitnami/kafka:latest'
11 |
12 | container_name: 'kafkaPP26'
13 |
14 | ports:
15 | - '9092:9092'
16 | environment:
17 | - KAFKA_BROKER_ID=1
18 | - KAFKA_LISTENERS=PLAINTEXT://:9092
19 | - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092
20 | - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
21 | - ALLOW_PLAINTEXT_LISTENER=yes
22 | depends_on:
23 | - zookeeper
24 |
--------------------------------------------------------------------------------
/dummydbreq.js:
--------------------------------------------------------------------------------
1 | const db = require('./server/models/userModel')
2 |
3 | async function addtodb() {
4 | const queryString = {
5 | text: 'INSERT INTO data (message, partition) VALUES ($1, $2)',
6 | values: ['test', 'partitiontest'],
7 | rowMode: 'array'
8 | }
9 | await db.query(queryString);
10 | return;
11 | }
12 | addtodb()
13 | .catch(err => console.log(`console log error ${err}`))
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dekaf",
3 | "version": "0.0.1",
4 | "description": "A web app for Kafka metrics vizualisation and managment.",
5 | "main": "/src/index.tsx",
6 | "scripts": {
7 | "start": "cross-env NODE_ENV=production ts-node server/server.ts",
8 | "build": "cross-env NODE_ENV=production webpack",
9 | "dev": "concurrently \"cross-env NODE_ENV=development webpack serve\" \"cross-env NODE_ENV=development nodemon --exec 'ts-node' server/server.ts\"",
10 | "dev:hot": "concurrently \"cross-env NODE_ENV=development webpack serve\" \"cross-env NODE_ENV=development nodemon --exec 'ts-node' server/server.ts\"",
11 | "test": "jest --verbose"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/oslabs-beta/deKaf.git"
16 | },
17 | "keywords": [
18 | "kafka",
19 | "metrics",
20 | "visual",
21 | "d3",
22 | "data",
23 | "health"
24 | ],
25 | "author": "Billy Hepfinger, Jake Song, Achille Perducat, Noah Mattingly, Mike Feldman",
26 | "license": "ISC",
27 | "bugs": {
28 | "url": "https://github.com/oslabs-beta/deKaf/issues"
29 | },
30 | "homepage": "https://github.com/oslabs-beta/deKaf#readme",
31 | "dependencies": {
32 | "cookie-parser": "^1.4.5",
33 | "dotenv": "^9.0.2",
34 | "events": "^3.3.0",
35 | "express": "^4.17.1",
36 | "path": "^0.12.7",
37 | "pg": "^8.6.0",
38 | "react": "^17.0.2",
39 | "react-dom": "^17.0.2",
40 | "react-router-dom": "^5.2.0",
41 | "sass": "^1.32.13",
42 | "ts-node": "^9.1.1",
43 | "winston": "^3.3.3"
44 | },
45 | "babel": {
46 | "presets": [
47 | "@babel/preset-react",
48 | "@babel/preset-env",
49 | "@babel/preset-typescript"
50 | ]
51 | },
52 | "devDependencies": {
53 | "@babel/core": "^7.14.0",
54 | "@babel/preset-env": "^7.14.1",
55 | "@babel/preset-react": "^7.13.13",
56 | "@babel/preset-typescript": "^7.13.0",
57 | "@types/bcryptjs": "^2.4.2",
58 | "@types/d3": "^6.5.0",
59 | "@types/d3-axis": "^2.0.0",
60 | "@types/d3-selection": "^2.0.0",
61 | "@types/d3-shape": "^2.0.0",
62 | "@types/enzyme": "^3.10.8",
63 | "@types/express": "^4.17.11",
64 | "@types/jest": "^26.0.23",
65 | "@types/node": "^15.0.2",
66 | "@types/react": "^17.0.5",
67 | "@types/react-dom": "^17.0.4",
68 | "@types/supertest": "^2.0.11",
69 | "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
70 | "babel-loader": "^8.2.2",
71 | "bcryptjs": "^2.4.3",
72 | "concurrently": "^6.1.0",
73 | "cross-env": "^7.0.3",
74 | "css-loader": "^5.2.4",
75 | "d3": "^6.7.0",
76 | "d3-array": "^2.12.1",
77 | "d3-axis": "^2.1.0",
78 | "d3-scale": "^3.3.0",
79 | "d3-selection": "^2.0.0",
80 | "d3-shape": "^2.1.0",
81 | "enzyme": "^3.11.0",
82 | "enzyme-to-json": "^3.6.2",
83 | "file-loader": "^6.2.0",
84 | "jest": "^27.0.1",
85 | "kafkajs": "^1.15.0",
86 | "nodemon": "^2.0.7",
87 | "path": "^0.12.7",
88 | "pg": "^8.6.0",
89 | "recharts": "^2.0.9",
90 | "sass-loader": "^11.1.0",
91 | "style-loader": "^2.0.0",
92 | "supertest": "^6.1.3",
93 | "ts-loader": "^9.1.2",
94 | "typescript": "^4.2.4",
95 | "webpack": "^5.38.1",
96 | "webpack-cli": "^4.7.0",
97 | "webpack-dev-server": "^3.11.2"
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/server/controllers/cookieController.ts:
--------------------------------------------------------------------------------
1 | /* Path to databse*/
2 | const dbCookie = require("../models/userModel");
3 |
4 | const cookieController = {
5 | //middleware to create a cookie and add that cookie to user
6 | createSessionCookie(req, res, next) {
7 | console.log('********* COOKIE CONTROLLER START *********')
8 | // console.log(req.body)
9 | const { username } = req.body;
10 |
11 | const newSSID = () => {
12 | const SSID = Math.floor(Math.random() * 1000000000000000);
13 | dbCookie.query(`SELECT ssid FROM sessions`)
14 | .then(data => {
15 | console.log(data.rows);
16 | for (let session of data.rows) {
17 | if (session.ssid === SSID) return newSSID();
18 | }
19 | console.log(SSID);
20 | setCookie(SSID);
21 | })
22 | }
23 |
24 | const setCookie = (SSID) => {
25 |
26 | res.cookie('SSID', SSID, { httpOnly: false, maxAge: 1000000 })
27 |
28 | const queryString: string =
29 | `INSERT INTO sessions(ssid, uuid)
30 | VALUES ($1, $2)`,
31 | queryArgs = [SSID, username];
32 |
33 | //const queryString = 'INSERT INTO sessions (ssid, username) VALUES ($1, $2)';
34 |
35 | // server is running on 8080, if you want to test
36 |
37 | dbCookie.query(queryString, queryArgs)
38 | .then(() => next())
39 | .catch(err => console.log('MAJOR PROBLEM TRYING TO ADD INTO SESSIONS DB: ', err))
40 | }
41 |
42 | newSSID();
43 | },
44 |
45 | //middleware to validate if the user has a cookie and if that cookie is a valid cookie to login
46 | sessionValidation(req, res, next) {
47 | console.log('entered verifySession')
48 | if (!req.cookies.SSID) return res.status(200).json({ message: 'failed' });
49 |
50 | console.log('ssid cookie detected, verfifying...')
51 | const { SSID } = req.cookies;
52 |
53 | const queryString: string =
54 | `SELECT ssid FROM sessions
55 | WHERE ssid = $1`,
56 | queryArgs = [SSID];
57 |
58 | console.log('checking for session in db');
59 | dbCookie.query(queryString, queryArgs)
60 | .then(data => {
61 | console.log('data:')
62 | console.log(data)
63 | //if nothing is returned then the ssid does not exist in the db and it is not a valid user
64 | console.log('before check ssid')
65 | if (data.rows[0] === undefined) return res.status(200).json('failed')
66 | console.log('after check ssid')
67 | res.locals.ssid = SSID;
68 | return next();
69 | })
70 | },
71 |
72 | deleteSessionCookie(req, res, next) {
73 | const { SSID } = req.cookies;
74 |
75 | const queryString: string =
76 | `DELETE FROM sessions
77 | WHERE ssid = $1`,
78 | queryArgs = [SSID];
79 |
80 | console.log('checking for session in db');
81 | dbCookie.query(queryString, queryArgs)
82 | .then(data => {
83 | //if nothing is returned then the ssid does not exist in the db and it is not a valid user
84 | res.clearCookie('SSID');
85 | return next();
86 | })
87 |
88 | },
89 |
90 | //add this middleware before all middleware
91 | //add column
92 | getUserFromSSID(req, res, next) {
93 |
94 | const { ssid } = res.locals;
95 |
96 | const query = {
97 | text: `SELECT uuid FROM sessions WHERE ssid = $1`,
98 | values: [ssid]
99 | }
100 | dbCookie.query(query)
101 | .then(data => {
102 | if (!data.rows.length) return res.status(200).json({ message: 'invalid ssid' })
103 | res.locals.username = data.rows[0].uuid;
104 | return next();
105 | })
106 | }
107 | };
108 |
109 | module.exports = cookieController;
110 |
--------------------------------------------------------------------------------
/server/controllers/kafkaController.ts:
--------------------------------------------------------------------------------
1 | // import topic from "../kafkaApplication/topic";
2 |
3 | import { csvParseRows } from "d3-dsv";
4 | import producer from "../kafkaApplication/producer";
5 |
6 | /* Path to databse*/
7 | const dbKafka = require('../models/userModel.ts');
8 |
9 | console.log('here')
10 | //kafka application files
11 | const topicKafka = require('../kafkaApplication/topic');
12 | const producerKafka = require('../kafkaApplication/producer');
13 | const consumerKafka = require('../kafkaApplication/consumer');
14 |
15 | const kafkaController = {
16 | starttopic(req, res, next) {
17 |
18 | //grabbing the broker data which consists of the kafka port, topic names, quantity of partitions in each topic and replication factor
19 | const { brokerData } = req.body;
20 | const { username } = res.locals;
21 | brokerData['username'] = username;
22 | //Connecting our chassis to the existing kafka instances in order to grab metrics
23 | topicKafka.run(brokerData);
24 |
25 | return next();
26 | },
27 |
28 | startproducer(req, res, next) {
29 | const { producerData } = req.body;
30 | console.log('producer Data:')
31 | console.log(producerData)
32 | console.log('in start producer')
33 | const { username } = res.locals;
34 | producerData['username'] = username;
35 | producerKafka.generateMessages(producerData);
36 | return next();
37 | },
38 |
39 | startconsumer(req, res, next) {
40 | const { consumerData } = req.body;
41 | const { username } = res.locals;
42 | console.log('username')
43 | console.log(username)
44 | consumerData['username'] = username;
45 | console.log('here in start consumer')
46 | consumerKafka.run(consumerData);
47 | return next();
48 | },
49 |
50 | async getMessageData(req, res, next) {
51 | // let messageCounter = 0;
52 | console.log('in the get message data')
53 | const { username } = res.locals;
54 | const messageQueryString = {
55 | text: `SELECT message_data AS messageData FROM consumers WHERE username = ($1)`,
56 | values: [username],
57 | rowMode: 'array'
58 | }
59 | const messageData = await dbKafka.query(messageQueryString);
60 | // console.log(messageData.rows);
61 | let messageCounter = messageData.rowCount;
62 | const messageDataArray = [];
63 | messageData.rows.forEach((el) => {
64 | messageDataArray.push(el[0])
65 | })
66 | // console.log(messageDataArray)
67 | res.locals.messageData = messageDataArray;
68 | res.locals.messageCounter = messageCounter;
69 | // console.log('test')
70 | return next();
71 | },
72 |
73 | async getRequestData(req, res, next) {
74 | const { username } = res.locals;
75 | const requestQueryString = {
76 | text: `SELECT request_data AS requestData FROM consumer_requests WHERE username = ($1)`,
77 | values: [username],
78 | rowMode: 'array'
79 | }
80 | const requestData = await dbKafka.query(requestQueryString);
81 | // console.log(requestData);
82 | let requestCounter = requestData.rowCount;
83 | const requestDataArray = [];
84 | requestData.rows.forEach((el) => {
85 | requestDataArray.push(el[0])
86 | })
87 | // console.log(requestDataArray)
88 | res.locals.requestData = requestDataArray;
89 | res.locals.requestCounter = requestCounter;
90 | return next();
91 | },
92 |
93 | async getProducerData(req, res, next) {
94 | const { username } = res.locals;
95 | const producerQueryString = {
96 | text: `SELECT request_data AS requestData FROM producer WHERE username = ($1)`,
97 | values: [username],
98 | rowMode: 'array'
99 | }
100 | const producerData = await dbKafka.query(producerQueryString);
101 | console.log(producerData);
102 | let producerCounter = producerData.rowCount;
103 | const producerDataArray = [];
104 | producerData.rows.forEach((el) => {
105 | producerDataArray.push(el[0])
106 | })
107 | res.locals.producerData = producerDataArray;
108 | res.locals.producerCounter = producerCounter;
109 | return next();
110 | },
111 |
112 | async getTopicData(req, res, next) {
113 | const { username } = res.locals;
114 | const topicQueryString = {
115 | text: `SELECT broker_data AS brokerData FROM brokers WHERE username = ($1)`,
116 | values: [username],
117 | rowMode: 'array'
118 | }
119 | const topicData = await dbKafka.query(topicQueryString);
120 | const topicDataArray = [];
121 | const numOfPartitions = [];
122 | let temp;
123 | let topicCounter = topicData.rowCount;
124 | console.log('topicCounter')
125 | console.log(topicData.rows[0][topicData.rows.length - 1])
126 | topicData.rows.forEach((el) => {
127 | temp = el[0].fetchTopicMetadata.topics;
128 | topicDataArray.push(el[0])
129 | });
130 | console.log(temp)
131 | temp.forEach((el) => {
132 | numOfPartitions.push({name: el.name, partitionQuantity: el.partitions.length})
133 | })
134 | res.locals.partitionQuantity = numOfPartitions;
135 | res.locals.topicData = topicDataArray;
136 | res.locals.topicCounter = topicCounter;
137 | return next();
138 | },
139 |
140 | async totalDataInPartition(req, res, next) {
141 | const { username } = res.locals;
142 | const partitionQueryString = {
143 | text: `SELECT partition FROM consumers WHERE username = ($1)`,
144 | values: [username],
145 | rowMode: 'array'
146 | }
147 | const totalPartitions = await dbKafka.query(partitionQueryString)
148 | const partitionSet = new Set();
149 | totalPartitions.rows.forEach((el) => {
150 | partitionSet.add(el[0])
151 | })
152 | const partitionArray = [...partitionSet];
153 | const partitionObject = {}
154 | console.log('Partition Array:')
155 | console.log(partitionArray)
156 | async function getData(partitionArray) {
157 | for (let i = 0; i < partitionArray.length; i++) {
158 | if (partitionArray[i] !== null) {
159 | let totalDataInPartitionQueryString = {
160 | text: `SELECT * FROM consumers WHERE partition = ($1) AND username = ($2)`,
161 | values: [partitionArray[i], username],
162 | rowMode: 'array'
163 | }
164 | let result = await dbKafka.query(totalDataInPartitionQueryString);
165 | // console.log(result.rows)
166 | partitionObject[partitionArray[i]] = result.rows.length;
167 | }
168 | }
169 | console.log(partitionObject)
170 | res.locals.quantityOfDataInEachPartition = partitionObject;
171 | return next()
172 |
173 | }
174 | getData(partitionArray)
175 | .catch(e => console.log('in the error of getData in totalDataInPartition', e));
176 | },
177 |
178 | async getMessageLag (req, res, next) {
179 | const { username } = res.locals;
180 | const producerQueryString = {
181 | text: `SELECT timestamp, messageid FROM producer WHERE username = ($1)`,
182 | values: [username],
183 | rowMode: 'array'
184 | }
185 | const consumerQueryString = {
186 | text: `SELECT timestamp, messageid FROM consumer_requests WHERE username = ($1)`,
187 | values: [username],
188 | rowMode: 'array'
189 | }
190 | let producerData = await dbKafka.query(producerQueryString);
191 | let requestData = await dbKafka.query(consumerQueryString);
192 | console.log('producerData')
193 | console.log(producerData.rows)
194 | producerData = producerData.rows;
195 | const realProducerData = [];
196 | console.log('real prod');
197 | console.log(realProducerData)
198 | console.log('requestData')
199 | console.log(requestData.rows)
200 | requestData = requestData.rows;
201 | const lagArray = [];
202 | let lagObject = {};
203 | let storedObj = {};
204 | let storedProdObj = {};
205 | producerData.forEach((el) => {
206 | if (!storedProdObj[el[1]]) {
207 | storedProdObj[el[1]] = true;
208 | realProducerData.push(el)
209 | }
210 | })
211 | let realRequestData = [];
212 | console.log('RP:')
213 | console.log(realProducerData)
214 | //consumer request object is one greater than producer there is one duplicate
215 | requestData.forEach((el, index) => {
216 | if (!storedObj[el[1]]) {
217 | storedObj[el[1]] = true;
218 | realRequestData.push(el)
219 | // lagObject['messageId'] = el[1];
220 | // lagObject['lag'] = el[0] - realProducerData[index][0];
221 | // lagArray.push(lagObject);
222 | // lagObject = {};
223 | }
224 | })
225 | realRequestData.forEach((el, index) => {
226 | lagObject['messageId'] = el[1];
227 | lagObject['lag'] = el[0] - realProducerData[index][0];
228 | lagArray.push(lagObject);
229 | lagObject = {};
230 | })
231 | console.log('DP:');
232 | console.log(realRequestData)
233 | console.log('lag')
234 | console.log(lagArray)
235 | res.locals.lag = lagArray;
236 | return next();
237 | }
238 |
239 | };
240 |
241 |
242 |
243 | module.exports = kafkaController;
--------------------------------------------------------------------------------
/server/controllers/userController.ts:
--------------------------------------------------------------------------------
1 |
2 | /* Path to databse*/
3 | const dbUser = require('../models/userModel');
4 | import * as bcrypt from 'bcryptjs';
5 |
6 | const userController = {
7 | createUser(req, res, next) {
8 | console.log('in createUser');
9 | console.log('checking if user exists');
10 | const
11 | queryString1: string = `
12 | SELECT username FROM users
13 | WHERE username=$1`,
14 | queryArgs1: string[] = [req.body.username];
15 | dbUser.query(queryString1, queryArgs1, (err, user) => {
16 | console.log(user.rows)
17 | if (user.rows.length!==0) return res.status(400).json('userExists');
18 | console.log('no duplicate, now creating user');
19 | const salt = bcrypt.genSaltSync(10);
20 | console.log(req.body.password);
21 | const hash = bcrypt.hashSync(req.body.password, salt);
22 | console.log(hash);
23 | const
24 | queryString2: string = `
25 | INSERT INTO users (username, password)
26 | VALUES ($1,$2) RETURNING *`,
27 | queryArgs2: string[] = [req.body.username, hash];
28 | dbUser.query(queryString2, queryArgs2, (err, user) => {
29 | if (err) return next({ log: err });
30 | console.log('finished query:', user.rows[0]);
31 | res.locals.userID = user.rows[0]['_id'];
32 | console.log(res.locals.userID);
33 | return next();
34 | });
35 | })
36 | },
37 | //midleware to verify that it's a valid user after logging in
38 | verifyUser(req, res, next) {
39 | //creating a query to grab the username from the req body and checking in the db if it is a valid username
40 | const
41 | queryString: string = `
42 | SELECT * FROM users
43 | WHERE username=$1`,
44 | queryArgs: string[] = [req.body.username];
45 |
46 | dbUser.query(queryString, queryArgs, (err, user) => {
47 | //if our returned query has nothing in the rows key then db did not find the user and it is not a valid user
48 | if (user.rows.length===0) return res.status(400).json('unkUser');
49 | //if the username is a username in the db then check that password the user entered matches the password in the db for that username
50 | bcrypt.compare(req.body.password, user.rows[0].password, (err, isMatch) => {
51 | if (err) console.log('Error in bcrypt hashing, verifyUser: ', err)
52 | //if not a password match then not a valid user
53 | if (!isMatch) return res.status(200).json({message: 'notMatching'});
54 | //if password is correct then grab the id from that user in the db and pass it along in the middleware
55 | res.locals.userID = user.rows[0]['_id'];
56 | return next();
57 | })
58 | })
59 | }
60 | };
61 |
62 | module.exports = userController;
--------------------------------------------------------------------------------
/server/dataStructures/queue.js:
--------------------------------------------------------------------------------
1 | const db = require('../models/userModel')
2 |
3 | class dataStructures {
4 | constructor() {
5 | this.messageArray = [];
6 | }
7 |
8 | queue(data) {
9 | this.messageArray.push(data);
10 | console.log(this.messageArray)
11 | // addToDb(data)
12 | }
13 |
14 | print(){
15 | console.log(this.messageArray)
16 | }
17 | }
18 |
19 | const test = new dataStructures()
20 | test.print()
21 |
22 | async function addToDb(data) {
23 | const { value, partition } = data;
24 | const queryString = {
25 | text: 'INSERT INTO data (message, partition) VALUES ($1, $2)',
26 | values: [value, partition],
27 | rowMode: 'array'
28 | }
29 | await db.query(queryString);
30 | return;
31 | }
32 | // caddToDb({})
33 |
34 | module.exports = dataStructures;
35 | // const queue = {
36 | // messageArray: []
37 | // };
38 |
39 | // queue.add = (currentMessage) => {
40 | // console.log('in the queue')
41 | // this.messageArray.push(currentMessage);
42 | // console.log(messageArray)
43 | // return;
44 | // }
--------------------------------------------------------------------------------
/server/dataStructures/test.js:
--------------------------------------------------------------------------------
1 | const dataStructures = require('../dataStructures/queue.js')
2 | const db = require('../models/userModel');
3 | function test() {
4 | const messageQ = 'this is a big test';
5 | const partition = 25;
6 | const queryString = {
7 | text: 'INSERT INTO data2 (message, partition) VALUES ($1, $2)',
8 | values: [messageQ, partition],
9 | rowMode: 'array'
10 | }
11 | console.log('before query')
12 | db.query(queryString)
13 | .catch(e => console.log(`error in addTodb`, e));
14 | // const buffer = new dataStructures()
15 | // buffer.print();
16 | }
17 |
18 |
19 | // test()
--------------------------------------------------------------------------------
/server/kafkaApplication/consumer.js:
--------------------------------------------------------------------------------
1 | const { Kafka } = require('kafkajs');
2 | const { logLevel } = require('kafkajs')
3 | const winston = require('winston')
4 | const db = require('../models/userModel.ts');
5 | // const topic = require('./topic');
6 | const EventEmitter = require('events');
7 | const { least } = require('d3-array');
8 |
9 |
10 | //initializing a consumer object
11 | const consumer = {}
12 | console.log('in consumer file')
13 | class MyEmitter extends EventEmitter {};
14 | //everything in this function will be the consumer logic
15 | consumer.run = async (consumerData) => {
16 | let { port, topics, userId, username} = consumerData;
17 | if (userId === undefined) userId = 3;
18 | try
19 | {
20 | //this winston function will be to track errors and store them in a log
21 | const toWinstonLogLevel = level => { switch(level) {
22 | case logLevel.ERROR:
23 | case logLevel.NOTHING:
24 | return 'error'
25 | case logLevel.WARN:
26 | return 'warn'
27 | case logLevel.INFO:
28 | return 'info'
29 | case logLevel.DEBUG:
30 | return 'debug'
31 | }
32 | }
33 |
34 | const WinstonLogCreator = logLevel => {
35 | const logger = winston.createLogger({
36 | level: toWinstonLogLevel(logLevel),
37 | transports: [
38 | new winston.transports.Console(),
39 | new winston.transports.File({ filename: 'myapp.log' })
40 | ]
41 | })
42 |
43 | return ({ namespace, level, label, log }) => {
44 | const { message, ...extra } = log
45 | console.log('test')
46 | logger.log({
47 | level: toWinstonLogLevel(level),
48 | message,
49 | extra,
50 | })
51 | }
52 | }
53 |
54 | //initializing a new kafka object
55 | const kafka = new Kafka({
56 | clientId: 'my-app',
57 | brokers: [`:${port}`],
58 | logLevel: logLevel.ERROR,
59 | logCreator: WinstonLogCreator
60 | })
61 |
62 | //connecting the kafka object to the consumer
63 | const consumer = kafka.consumer({
64 | groupId: 'my-group',
65 | sessionTimeout: 30000,
66 | })
67 |
68 | console.log('connection to consumer...')
69 | //connecting consumer
70 | await consumer.connect();
71 | console.log('connected to consumer')
72 |
73 | //subscribing this consumer to the topic
74 | console.log(topics)
75 | const subscribingToTopics = (topics) => {
76 | let topicObject = {}
77 | topics.forEach( async (el) => {
78 | topicObject['topic'] = el;
79 | topicObject['fromBeginning'] = true;
80 | console.log('subscribing to topic')
81 | await consumer.subscribe(topicObject)
82 | console.log('subscribed')
83 | topicObject = {};
84 | }
85 | // .catch(err => console.log('there was an error when subscribing to topic ', err))
86 | )
87 |
88 | }
89 | await subscribingToTopics(topics)
90 |
91 |
92 | console.log('logger')
93 | const logger = await consumer.logger().info();
94 | console.log(logger)
95 |
96 | //running the consumer to collect the data being sent from the producer
97 | //this will be used if the producer wants to send messages in batches
98 | await consumer.run({
99 | // eachMessage: console.log('in each message'),
100 | eachBatchAutoResolve: true,
101 | 'eachBatch': async ({
102 | batch,
103 | resolveOffset,
104 | heartbeat,
105 | commitOffsetsIfNecessary,
106 | uncommittedOffsets,
107 | isRunning,
108 | isStale,
109 | }) => {
110 | console.log('in the batch messages')
111 |
112 | const { messageId, message } = getMessageId(batch)
113 |
114 | const { partition, topic, fetchedOffset} = batch;
115 |
116 | const dataId = await mainMessageQueryFunc(topic, partition, message, userId);
117 | // // // console.log(dataId);
118 |
119 | // //deconstructing some of the instances events associated with the consumer to gather future data
120 | const { REQUEST, FETCH, GROUP_JOIN, START_BATCH_PROCESS } = consumer.events;
121 |
122 | // //gathering data from the request event
123 | requestFunc(REQUEST, dataId, messageId);
124 |
125 | // //gathering data from the fetch event
126 | // const fetch = fetchFunc(FETCH, dataId);
127 |
128 | // //gathering data on groupJoin event
129 | // const groupJoin = groupJoinFunc(GROUP_JOIN, dataId)
130 |
131 | // //gathering data on the batch request event
132 | // const batchReqest = await startBatchProcessFun(START_BATCH_PROCESS, dataId)
133 |
134 | await heartbeat()
135 | }
136 |
137 | })
138 |
139 | //helper function to grab the messageId sent from the producer
140 |
141 | function getMessageId(batch) {
142 | const testMessage = batch.messages[0].value.toString();
143 | const arr = testMessage.split(',');
144 | const messageId = parseInt(arr.pop());
145 | console.log(messageId)
146 | let newString = arr.join(',')
147 | let message = newString.slice(1, newString.length)
148 | return {messageId: messageId, message: message}
149 | }
150 |
151 |
152 | /**** Methods to grab data pertaining to the messages and uploading to the database for the client to use *****/
153 |
154 | //sending topic, partition, message and userId data to the consumer table
155 | async function mainMessageQueryFunc(topic, partition, message, userId) {
156 | console.log('in the message func')
157 | const messageData = {
158 | value: message,
159 | partition: partition,
160 | topic: topic
161 | }
162 | const queryString = {
163 | text: 'INSERT INTO consumers (user_id, message_data, partition, username) VALUES ($1, $2, $3, $4) RETURNING _id AS dataId',
164 | values: [userId, messageData, partition, username],
165 | rowMode: 'array'
166 | }
167 | console.log('before query')
168 | const result = await db.query(queryString)
169 | .catch(e => console.log(`error in addTodb`, e));
170 | const dataId = result.rows[0][0];
171 | return dataId;
172 | }
173 |
174 | //sending information about the request data and the unique messageId to the consumer_requests SQL table
175 | async function requestFunc(REQUEST, dataId, messageId) {
176 |
177 | let eventOn = false;
178 | const req = await consumer.on(REQUEST, async (e) => {
179 | console.log('in the request fun')
180 |
181 | const { timestamp, payload } = e;
182 | let time = timestamp.toString();
183 |
184 | const queryString = {
185 | text: 'INSERT INTO consumer_requests (request_data, data_id, messageid, timestamp, username) VALUES ($1, $2, $3, $4, $5)',
186 | values: [payload, dataId, messageId, time, username],
187 | rowMode: 'array'
188 | }
189 | console.log('before query')
190 | await db.query(queryString)
191 | .catch(e => console.log(`error in addTodb`, e));
192 |
193 | req();
194 | })
195 |
196 | }
197 |
198 | async function startBatchProcessFun(START_BATCH_PROCESS, dataId) {
199 | const startBatch = consumer.on(START_BATCH_PROCESS, async (e) => {
200 | console.log('in the start batch')
201 | console.log(e)
202 | })
203 | // startBatch()
204 | }
205 |
206 | function fetchFunc(FETCH) {
207 | consumer.on(FETCH, (e) => {
208 | console.log('in the fetch func')
209 | // console.log(e)
210 | return e
211 | })
212 | }
213 |
214 | function groupJoinFunc(GROUP_JOIN, dataId) {
215 | consumer.on(GROUP_JOIN, (e) => {
216 | console.log(e)
217 | })
218 | }
219 | }
220 |
221 |
222 | catch(e) {
223 | console.log(`Something bad happened in the consumer ${e}`)
224 | }
225 | finally {
226 | console.log('Finished consumer script')
227 |
228 | }
229 | }
230 |
231 |
232 | module.exports = consumer;
233 |
234 |
235 |
236 |
--------------------------------------------------------------------------------
/server/kafkaApplication/producer.js:
--------------------------------------------------------------------------------
1 | const { Kafka } = require('kafkajs');
2 |
3 | const db = require('../models/userModel.ts');
4 | const mockData = require('./mockData')
5 | // const topic = require('./topic');
6 | // const queue = require('../dataStorage/queue.js');
7 |
8 | const producer = {};
9 |
10 |
11 | console.log('in producer file')
12 | producer.generateMessages = (producerData) => {
13 | const { random } = producerData;
14 | if (random === true) {
15 | setInterval(() => {
16 | queueRandomMessage(producerData)
17 | }, 6500)
18 | }
19 | else {
20 | let dataMessage;
21 | run(dataMessage, producerData);
22 | }
23 | }
24 | // }
25 | // whileLoppFunc();
26 | // }
27 |
28 | async function run(dataMessage, producerData) {
29 | console.log('in the producer.run!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
30 | console.log(dataMessage)
31 | const { port, topics, username } = producerData
32 | try {
33 | const kafka = new Kafka({
34 | clientId: 'my-app',
35 | brokers: [`:${port}`]
36 | })
37 |
38 | const producer = kafka.producer();
39 | console.log('connecting to producer');
40 | await producer.connect();
41 | console.log('connected to producer');
42 | if (dataMessage) {
43 | // console.log('logger')
44 | // const logger = await producer.logger().info();
45 | // console.log(logger)
46 | // function randomId () {
47 | const messageId = Math.ceil(Math.random() * 1000000000000)
48 | // }
49 | // console.log('producer events');
50 | // console.log(producer.events);
51 | const { REQUEST } = producer.events;
52 | const prod = producer.on(REQUEST, async (e) => {
53 | // console.log(e);
54 |
55 | const { timestamp, payload } = e;
56 | let time = timestamp.toString();
57 | const queryString = {
58 | text: `INSERT INTO producer (request_data, messageId, timestamp, username) VALUES ($1, $2, $3, $4)`,
59 | values: [payload, messageId, time, username],
60 | rowMode: 'array'
61 | }
62 | await db.query(queryString);
63 | console.log('producer added to DB')
64 | prod();
65 | })
66 |
67 |
68 | //partition logic:
69 | // ['Mike', 'Jake', 'Billy', 'Noah', 'Achille']
70 | // let partition;
71 | // const { teammates } = dataMessage;
72 | // console.log(teammates)
73 | // if (teammates === 'Mike') partition = 0;
74 | // else if (teammates === 'Jake') partition = 1;
75 | // else if (teammates === 'Billy') partition = 2;
76 | // else if (teammates === 'Noah') partition = 3;
77 | // else if (teammates === 'Achille') partition = 4;
78 |
79 |
80 | //sending a batch of messages to multiple topics:
81 |
82 | // const topicMessages = [
83 | // {
84 | // topic: 'topic',
85 | // message: [{key: 'key', value: 'something'}]
86 | // }
87 | // ]
88 | // await producer.sendBatch({topicMessages})
89 |
90 | // message : "MESSAGE"
91 | // message: ["MESSAGE", dataId]
92 | // //input that timestamp into that dataId's column
93 | // // at the point of output, just subtract that from message[2]
94 | // message[0], message[1] < --- message[1]
95 | //random Id and timestamp
96 |
97 | // producer -> dataId & all of the producerData
98 | // consumer -> dataId & all of the consumerData
99 |
100 | //grab timeStamp WHERE dataId =
101 |
102 | //in consumer, cross examine random Id from
103 | //[ randomId |timestamp in | timestamp out ]
104 | //on the consumer, SELECT randomId WHERE = randomId of the latest received consumer data
105 | //upon reception, input the second timestamp and then subtract from the first
106 | //include that as latency in output middleware
107 | const topicMessagesArray = (topics) => {
108 | let newMessage = [dataMessage, messageId]
109 | let topicArray = [];
110 | let topicObject = {};
111 | topics.forEach((el) => {
112 | topicObject['topic'] = el;
113 | topicObject['messages'] = [{key: 'key', value: JSON.stringify(newMessage)}]
114 | topicArray.push(topicObject);
115 | topicObject = {};
116 | })
117 | return topicArray;
118 | }
119 | const topicMessages = topicMessagesArray(topics)
120 | // console.log(topicArray)
121 | console.log('sending producer message')
122 | // let newMessage = [dataMessage, messageId]
123 | // const topicMessages = [
124 | // {
125 | // topic: 'RandomGeneratedData',
126 | // messages: [
127 | // {
128 | // key: 'key',
129 | // value: JSON.stringify(newMessage),
130 | // // partition: partition
131 | // }
132 | // ]
133 | // }
134 | // // {
135 | // // topic: 'thisIsATest',
136 | // // messages: [
137 | // // {
138 | // // value: JSON.stringify('hello')
139 | // // }
140 | // // ]
141 | // // }
142 | // ]
143 | await producer.sendBatch({ topicMessages })
144 |
145 | // await producer.send({
146 | // topic: 'RandomGeneratedData',
147 | // messages: [
148 | // {
149 | // value: JSON.stringify(dataMessage),
150 | // // partition: partition
151 | // }
152 | // ]
153 | // })
154 | }
155 | }
156 | catch (e) {
157 | console.log(`Something bad happened in the producer ${e}`)
158 | }
159 | finally {
160 | // process.exit(0)
161 | }
162 | }
163 |
164 | function queueRandomMessage(producerData) {
165 | const randomDataNum = Math.ceil(Math.random() * 700)
166 | console.log('randomNum:')
167 | console.log(randomDataNum)
168 | const { data } = mockData;
169 | const dataMessage = data[randomDataNum];
170 | console.log('mock data has been created')
171 | run(dataMessage, producerData);
172 | }
173 |
174 | console.log('end of producer')
175 | module.exports = producer;
176 |
--------------------------------------------------------------------------------
/server/kafkaApplication/topic.js:
--------------------------------------------------------------------------------
1 | const { Kafka } = require('kafkajs');
2 |
3 | // import db from '../models/userModel';
4 | const db = require('../models/userModel.ts')
5 | // const queue = require('../dataStorage/queue.js');
6 |
7 | const topic = {};
8 | // run();
9 | console.log('in the topic')
10 | topic.run = async ({port, topicData, username}) => {
11 | // console.log(topicName)
12 | // const { topicName } = topicName;
13 | try{
14 | const kafka = new Kafka({
15 | clientId: 'my-app',
16 | // ssl: true,
17 | brokers: [`:${port}`]
18 | })
19 | console.log('creating kafka admin');
20 |
21 | const admin = kafka.admin();
22 | console.log('connecting to admin')
23 |
24 | await admin.connect();
25 | let tempTopicData = [{
26 | topic: 'RandomGeneratedData',
27 | partition: 5,
28 | replicationFactor: 1
29 | }]
30 | topicDataArray = [];
31 | topicDataObj = {};
32 | // {topicName, partition, replicationFactor}
33 | console.log(topicData)
34 | topicData.forEach((el) => {
35 | // console.log(el);
36 | const { topicName, partition, replicationFactor } = el;
37 | console.log(partition);
38 | topicDataObj['topic'] = topicName;
39 | topicDataObj['numPartitions'] = partition;
40 | topicDataObj['replicationFactor'] = replicationFactor;
41 | topicDataArray.push(topicDataObj);
42 | topicDataObj = {};
43 | })
44 | // console.log(topicDataArray)
45 | console.log('creating new topics')
46 | await admin.createTopics({
47 | topics: topicDataArray
48 | // topics: [
49 | // {
50 | // topic: `${topicName}`,
51 | // numPartitions: `${partition}`,
52 | // replicationFactor: `${replicationFactor}`
53 | // // replicaAssignment: []
54 | // },
55 | // ]
56 | })
57 | console.log('topic created successfully');
58 |
59 | console.log('seeing topics assigned to this admin');
60 | const listTopics = await admin.listTopics();
61 | console.log('list topics');
62 | console.log(listTopics)
63 |
64 | console.log('fetch topic metaData')
65 | const fetchTopicMetadata = await admin.fetchTopicMetadata()
66 | // console.log(fetchTopicMetadata)
67 | // console.log(fetchTopicMetadata.topics[2])
68 | const partionData = fetchTopicMetadata.topics[1];
69 | // console.log(partionData.partitions[3])
70 | // console.log(partionData[0])
71 |
72 | console.log('describing cluster');
73 | const describeCluster = await admin.describeCluster();
74 | console.log(describeCluster)
75 | const deleteQueryString = {
76 | text: `DELETE FROM brokers WHERE username = ($1)`,
77 | values: [username],
78 | rowMode: 'array'
79 | }
80 | const topicQueryString = {
81 | text: `INSERT INTO brokers (broker_data, username) VALUES ($1, $2)`,
82 | values: [{listTopics: listTopics, fetchTopicMetadata: fetchTopicMetadata, describeCluster: describeCluster}, username],
83 | rowMode: 'array'
84 | };
85 | await db.query(deleteQueryString)
86 | await db.query(topicQueryString)
87 | // .catch(e => 'error adding topic into db ', e)
88 | // const data = {value: 'hello', partition: 2};
89 | // const queryString = {
90 | // text: 'INSERT INTO data (message, partition) VALUES ($1, $2)',
91 | // values: ['hello', 2],
92 | // rowMode: 'array'
93 | // }
94 | // console.log('dbing')
95 | // await db.query(queryString);
96 |
97 | // console.log('logger')
98 | // const logger = await admin.logger().info();
99 | // console.log(logger)
100 |
101 | console.log('disconnecting from admin');
102 | await admin.disconnect();
103 |
104 | }
105 | catch (e) {
106 | console.log(`Something bad happened in topic${e}`)
107 | }
108 | finally {
109 | console.log('in finally');
110 | // process.exit(0);
111 | }
112 | }
113 |
114 | // topic.run();
115 |
116 |
117 | module.exports = topic;
118 | // export default topic;
119 |
120 | /** Look into later for getting brokers from user **/
121 |
122 | // const kafka = new Kafka({
123 | // clientId: 'my-app',
124 | // brokers: async () => {
125 | // // Example getting brokers from Confluent REST Proxy
126 | // const clusterResponse = await fetch('https://kafka-rest:8082/v3/clusters', {
127 | // headers: 'application/vnd.api+json',
128 | // }).then(response => response.json())
129 | // const clusterUrl = clusterResponse.data[0].links.self
130 |
131 | // const brokersResponse = await fetch(`${clusterUrl}/brokers`, {
132 | // headers: 'application/vnd.api+json',
133 | // }).then(response => response.json())
134 |
135 | // const brokers = brokersResponse.data.map(broker => {
136 | // const { host, port } = broker.attributes
137 | // return `${host}:${port}`
138 | // })
139 |
140 | // return brokers
141 | // }
142 | // })
--------------------------------------------------------------------------------
/server/models/userModel.ts:
--------------------------------------------------------------------------------
1 | const { Pool } = require("pg");
2 | require('dotenv').config();
3 |
4 |
5 | //URI to elephantSQL database that will store the users favorite plants and any notes that add to those faves
6 | const PG_URI = process.env['PGURI'];
7 |
8 | //create a new pool here using the connection string above
9 | const pool = new Pool({
10 |
11 | connectionString: PG_URI,
12 |
13 | // connectionString: process.env['PGCONNECT'],
14 | // user: process.env['PGUSER'],
15 | // password: process.env['PGPASSWORD'],
16 | // host: process.env['PGHOST'],
17 | // database: process.env['PGDATABASE'],
18 | // port: process.env['PGPORT'],
19 | });
20 |
21 | // We export an object that contains a property called query,
22 | // which is a function that returns the invocation of pool.query() after logging the query
23 | // This will be required in the controllers to be the access point to the database
24 |
25 | // exporting module with some console logs
26 |
27 | module.exports = {
28 | query: async (text, params, callback) => {
29 | const client = await pool.connect();
30 | let res;
31 |
32 | try{
33 | await client.query('BEGIN');
34 | res = await client.query(text, params, callback);
35 | await client.query('COMMIT');
36 | } catch (e) {
37 | await client.query('ROLLBACK');
38 | console.error(e);
39 | } finally {
40 | console.log('in the userModel finally')
41 | client.release();
42 | }
43 | return res;
44 | },
45 | };
46 |
47 |
48 | // module.exports = {
49 | // query: (text, params, callback) => {
50 | // // console.log("executed query", text);
51 | // params = pool.connectionString;
52 | // // console.log("executed params", params);
53 | // // console.log("executed callback", callback);
54 | // return pool.query(text, params, callback);
55 | // },
56 | // };
57 |
58 | //send grid
59 |
--------------------------------------------------------------------------------
/server/routes/dbRouter.ts:
--------------------------------------------------------------------------------
1 | // //** Requiring in express.Router**//
2 |
3 | // const expressImport = require('express');
4 | // const routerDB = expressImport.Router();
5 |
6 | // //** Path to file controllers**//
7 | // const userControllerFiles = require("../controllers/userController.ts");
8 | // const dbControllerFiles = require("../controllers/dbController.ts");
9 | // const kafkaControllerFiles = require("../controllers/kafkaController.ts");
10 | // const cookieControllerFiles = require("../controllers/cookieController.ts");
11 |
12 | // // routerDB.get('/data');
13 |
14 | // module.exports = routerDB;
--------------------------------------------------------------------------------
/server/routes/kafkaRouter.ts:
--------------------------------------------------------------------------------
1 | //** Requiring in express.Router**//
2 |
3 | const expressKafka = require('express');
4 | const routerKafka = expressKafka.Router();
5 |
6 | //** Path to file controllers**//
7 | const userControllerKafka = require("../controllers/userController.ts");
8 | const kafkaControllerKafka = require("../controllers/kafkaController.ts");
9 | const cookieControllerKafka = require("../controllers/cookieController.ts");
10 | //
11 | routerKafka.post('/connectTopic', cookieControllerKafka.sessionValidation, cookieControllerKafka.getUserFromSSID, kafkaControllerKafka.starttopic, (req, res) => {
12 | console.log('done');
13 | res.sendStatus(200)
14 | })
15 |
16 | routerKafka.post('/connectProducer', cookieControllerKafka.sessionValidation, cookieControllerKafka.getUserFromSSID, kafkaControllerKafka.startproducer, (req, res) => {
17 | console.log('done in connect producer');
18 | res.sendStatus(200)
19 | })
20 |
21 | routerKafka.post('/connectConsumer', cookieControllerKafka.sessionValidation, cookieControllerKafka.getUserFromSSID, kafkaControllerKafka.startconsumer, (req, res) => {
22 | console.log('done');
23 | res.sendStatus(200)
24 | })
25 |
26 | routerKafka.get('/messageData', cookieControllerKafka.sessionValidation, cookieControllerKafka.getUserFromSSID, kafkaControllerKafka.getMessageData, (req, res) => {
27 | // console.log('done');
28 | const { messageData, messageCounter } = res.locals;
29 | console.log(messageCounter)
30 | res.status(200).json({messageData: messageData, messageCounter: messageCounter})
31 | });
32 |
33 | routerKafka.get('/requestData', cookieControllerKafka.sessionValidation, cookieControllerKafka.getUserFromSSID, kafkaControllerKafka.getRequestData, (req, res) => {
34 | console.log('in the request Data end of route');
35 | const { requestData, requestCounter } = res.locals;
36 | res.status(200).json({requestData: requestData, requestCounter: requestCounter})
37 | })
38 |
39 | routerKafka.get('/producerData', cookieControllerKafka.sessionValidation, cookieControllerKafka.getUserFromSSID, kafkaControllerKafka.getProducerData, (req, res) => {
40 | console.log('in the end of the producer data router');
41 | const { producerData, producerCounter } = res.locals;
42 | res.status(200).json({producerData: producerData, producerCounter: producerCounter})
43 | })
44 |
45 | routerKafka.get('/topicData', cookieControllerKafka.sessionValidation, cookieControllerKafka.getUserFromSSID, kafkaControllerKafka.getTopicData, kafkaControllerKafka.totalDataInPartition, (req, res) => {
46 | console.log('in the end of topic data');
47 | const { topicData, topicCounter, partitionQuantity, quantityOfDataInEachPartition } = res.locals;
48 | res.status(200).json({topicData: topicData, topicCounter: topicCounter, partitionQuantity: partitionQuantity, quantityOfDataInEachPartition: quantityOfDataInEachPartition})
49 | })
50 |
51 | routerKafka.get('/getLag', cookieControllerKafka.sessionValidation, cookieControllerKafka.getUserFromSSID, kafkaControllerKafka.getMessageLag, (req, res) => {
52 | console.log('done in lag');
53 | const { lag } = res.locals;
54 | res.status(200).json({lag: lag});
55 | })
56 |
57 | module.exports = routerKafka;
--------------------------------------------------------------------------------
/server/routes/userRouter.ts:
--------------------------------------------------------------------------------
1 | //** Requiring in express.Router**//
2 |
3 | const expressUser = require('express');
4 | const routerUser = expressUser.Router();
5 |
6 | //** Path to file controllers**//
7 | const userControllerUser = require("../controllers/userController.ts");
8 | const kafkaControllerUser = require("../controllers/kafkaController.ts");
9 | const cookieControllerUser = require("../controllers/cookieController.ts");
10 |
11 |
12 | routerUser.post('/signup', userControllerUser.createUser, cookieControllerUser.createSessionCookie, (req, res) => {
13 | res.status(200).json('success');
14 | });
15 |
16 | routerUser.post('/login', userControllerUser.verifyUser, cookieControllerUser.createSessionCookie, (req, res) => {
17 | res.status(200).json('success');
18 | });
19 |
20 | routerUser.post('/logout', cookieControllerUser.deleteSessionCookie, (req, res) => {
21 | res.status(200).json('success');
22 | });
23 |
24 | routerUser.get('/verifySession', cookieControllerUser.sessionValidation, (req, res) => {
25 | res.status(200).json('success');
26 | });
27 |
28 | module.exports = routerUser;
--------------------------------------------------------------------------------
/server/server.ts:
--------------------------------------------------------------------------------
1 | //** Express server imports **//
2 | const express = require('express');
3 | const cookieParser = require('cookie-parser');
4 | const app = express();
5 | const PORT = 3000;
6 |
7 | //** Additional imports **//
8 | const path = require('path');
9 |
10 | //require routers
11 | const kafkaRouter = require("./routes/kafkaRouter.ts");
12 | const userRouter = require("./routes/userRouter.ts");
13 |
14 | //** Serve all compiled files when running the production build **//
15 | app.use(express.static(path.resolve(__dirname, '../src')));
16 | app.use('/build', express.static(path.join(__dirname, '../build')));
17 |
18 | //** Automatically parse urlencoded body content from incoming requests and place it in req.body **//
19 | app.use(express.json());
20 | app.use(express.urlencoded({ extended:true }));
21 | app.use(cookieParser());
22 |
23 |
24 | //** Route handler to serve the basic file in case of no webpack build **//
25 | app.get('/', (req, res) => {
26 | return res.status(200).sendFile(path.join(__dirname, '../src/index.html'));
27 | });
28 |
29 | //functionality routes
30 | app.use('/user', userRouter);
31 | app.use('/kafka', kafkaRouter);
32 | // app.use('/db', dbRouter);
33 |
34 |
35 | //** Middleware to serve the main html file **//
36 | const serveMainFile = (req, res) => {
37 | return res.status(200).sendFile(path.join(__dirname, '../src/index.html'));
38 | }
39 |
40 | //** Routes requiring main file **//
41 | app.get('/', serveMainFile);
42 | app.get('/login', serveMainFile);
43 | app.get('/signup', serveMainFile);
44 | app.get('/about', serveMainFile);
45 | app.get('/user', serveMainFile);
46 | app.get('/details', serveMainFile);
47 | app.get('/history', serveMainFile);
48 |
49 | //Router for kafka related requests
50 | app.use('/kafka', kafkaRouter)
51 |
52 |
53 | //** No route / 404 Handler **//
54 | app.use('*', (req, res) => res.status(404).send('Error 404: This page doesn\'t exist!'));
55 |
56 | //** Global Error Handler **//
57 | app.use((err, req, res, next) => {
58 | const defaultErr = {
59 | log: 'Express error handler caught unknown middleware error',
60 | status: 500,
61 | message: { err: 'An error occurred' },
62 | };
63 | const errorObj = Object.assign({}, defaultErr, err);
64 | console.log(errorObj.log);
65 | return res.status(errorObj.status).json(errorObj.message);
66 | });
67 |
68 | app.listen(PORT, () =>{console.log(`Server is up and listening on port ${PORT}.`)});
69 |
70 | module.exports = app;
--------------------------------------------------------------------------------
/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import {select} from 'd3-selection';
3 | // @ts-ignore
4 | import Vis from './Vis.tsx';
5 | import { Route, Switch } from 'react-router-dom';
6 |
7 | // @ts-ignore
8 | import NavBar from './Navbar.tsx';
9 | // @ts-ignore
10 | import TopNav from './UserPage/TopNav.tsx';
11 | // @ts-ignore
12 | import Home from './Base/Home.tsx';
13 | // @ts-ignore
14 | import Login from './Base/Login.tsx';
15 | // @ts-ignore
16 | import Signup from './Base/Signup.tsx';
17 | // @ts-ignore
18 | import About from './Base/About.tsx';
19 | // @ts-ignore
20 | import UserPage from './UserPage/UserPage.tsx';
21 |
22 | const App = () => {
23 |
24 | return (
25 |
26 | {/*
*/}
27 |
28 | } />
29 | } />
30 | } />
31 | } />
32 |
33 |
34 |
35 | } />
36 | } />
37 | } />
38 | } />
39 | } />
40 | } />
41 | } />
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | export default App;
--------------------------------------------------------------------------------
/src/components/Base/About.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const About = () => {
5 | return (
6 |
7 |
8 |
9 |
10 |
Upgrade your Kafka experience with deKaf
11 |
deKaf is a data visualization tool that allows you to easily view current and past activity on your Kafka brokers.
12 |
13 |
14 |
17 |
18 | {/*
(Logo image will go here)
19 |
A visualization tool for Kafka consumer metrics
20 |
21 |
22 |
25 |
26 |
27 |
30 |
31 |
32 |
35 |
36 |
*/}
37 |
38 |
39 |
40 |
How to use deKaf
41 |
42 |
43 |
Using deKaf is easy. Once you've signed in or created an account, you'll be asked to provide the port where your Kafka server is running, as well as a few details about the Kafka topics you'd like to monitor. You can even choose to test your Kafka instance with random data, to make sure it's functioning properly.
44 |
On the Metrics Overview page, you can easily select which category you'd like to monitor: Topic, Messages, Consumer, or Producer. deKaf will connect to your Kafka instance and render your metrics dynamically via D3, updating regularly to make sure you have access to the latest data.
45 |
46 |
47 |
50 |
51 |
52 |
53 | {/*
54 |
Another panel
55 |
56 |
57 |
Content of another panel
58 |
59 |
60 |
61 |
Display of another panel
62 |
63 |
64 |
*/}
65 |
66 |
67 |
Contributors
68 |
75 |
76 |
77 | )
78 | }
79 |
80 | export default About;
--------------------------------------------------------------------------------
/src/components/Base/Home.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const Home = () => {
5 | return (
6 |
7 |
8 |
9 |
A web-based visualization tool for Kafka consumer metrics
10 |
11 |
12 |
15 |
16 |
17 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
Intuitive
33 |
Easily view key metrics on your Kafka instance with our intuitive web GUI.
34 |
35 |
36 |
37 |
Lightweight
38 |
No installation required; just enter your information and start using deKaf!
39 |
40 |
41 |
42 |
Real-time
43 |
Metrics update automatically as long as you're signed in, so you won't miss a beat.
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | export default Home;
--------------------------------------------------------------------------------
/src/components/Base/Login.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link, useHistory } from 'react-router-dom';
3 |
4 | const Login = () => {
5 |
6 | const history = useHistory();
7 | const [info, setInfo] = useState(null);
8 |
9 | useEffect (() => {
10 | fetch('/user/verifySession')
11 | .then(data => data.json())
12 | .then(data => {
13 | if (data === 'success') history.push('/user');
14 | })
15 | }, [])
16 |
17 | function onUserLogin(e) {
18 | e.preventDefault();
19 |
20 | const nameInput = document.getElementById('user') as HTMLInputElement;
21 | const pswdInput = document.getElementById('password') as HTMLInputElement;
22 |
23 | if (nameInput.value === '') return setInfo('Please enter a valid username.');
24 | if (pswdInput.value === '') return setInfo('Please enter a valid password.');
25 |
26 | console.log('Loggin in ', nameInput.value)
27 |
28 | fetch('/user/login', {
29 | method: 'POST',
30 | headers: { 'Content-Type': 'Application/JSON' },
31 | body: JSON.stringify({ username: nameInput.value, password: pswdInput.value })
32 | })
33 | .then(response => response.json())
34 | .then(data => {
35 | switch (data) {
36 | case 'unkUser':
37 | setInfo('User not found!');
38 | nameInput.value = '';
39 | pswdInput.value = '';
40 | break;
41 | case 'notMatching':
42 | setInfo('Wrong password!');
43 | pswdInput.value = '';
44 | break;
45 | case 'success':
46 | setInfo('Logging in...');
47 | setTimeout(() => {
48 | setInfo('');
49 | history.push('/user');
50 | }, 1000);
51 | break;
52 | default:
53 | throw new Error('Invalid Login: Server ERROR');
54 | }
55 | })
56 | .catch((error) => {
57 | console.error('Error when POST-fetching for login: ', error);
58 | })
59 | }
60 |
61 | return (
62 |
63 |
64 | {/*
A visualization tool for Kafka consumer metrics
*/}
65 |
66 |
67 |
Log in to deKaf
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
{info}
77 |
78 |
79 |
New to deKaf? Learn more or create an account.
80 |
81 | )
82 | }
83 |
84 | export default Login;
--------------------------------------------------------------------------------
/src/components/Base/Signup.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link, useHistory } from 'react-router-dom';
3 |
4 | const Signup = () => {
5 |
6 | const history = useHistory();
7 | const [info, setInfo] = useState(null);
8 |
9 | useEffect (() => {
10 | fetch('/user/verifySession')
11 | .then(data => data.json())
12 | .then(data => {
13 | if (data === 'success') history.push('/user');
14 | })
15 | }, [])
16 |
17 | function onUserLogin(e) {
18 | e.preventDefault();
19 |
20 | const nameInput = document.getElementById('user') as HTMLInputElement;
21 | const pswdInput = document.getElementById('password') as HTMLInputElement;
22 | const pswdConfInput = document.getElementById('passwordConf') as HTMLInputElement;
23 |
24 | if (nameInput.value === '') return setInfo('Please enter a valid username.');
25 | if (pswdInput.value === '') return setInfo('Please enter a valid password.');
26 | if (pswdConfInput.value === '') return setInfo('Please confirm your password.');
27 |
28 | if (pswdInput.value !== pswdConfInput.value) {
29 | pswdInput.value = '';
30 | pswdConfInput.value = '';
31 | return setInfo('Passwords don\'t match!');
32 | }
33 |
34 | console.log('Signing up ', nameInput.value)
35 |
36 | fetch('/user/signup', {
37 | method: 'POST',
38 | headers: { 'Content-Type': 'Application/JSON' },
39 | body: JSON.stringify({ username: nameInput.value, password: pswdInput.value })
40 | })
41 | .then(response => response.json())
42 | .then(data => {
43 | switch (data) {
44 | case 'userExists':
45 | setInfo('Username is already taken!');
46 | nameInput.value = '';
47 | pswdInput.value = '';
48 | pswdConfInput.value = '';
49 | break;
50 | case 'success':
51 | nameInput.value = '';
52 | pswdInput.value = '';
53 | pswdConfInput.value = '';
54 | setInfo('Account created!')
55 | setTimeout(() => {
56 | setInfo('');
57 | history.push('/user');
58 | }, 1000)
59 | break;
60 | default:
61 | throw new Error('Invalid Signup: Server ERROR');
62 | }
63 | })
64 | .catch((error) => {
65 | console.error('Error when POST-fetching for signup: ', error);
66 | })
67 | }
68 |
69 | return (
70 |
71 |
72 | {/*
A visualization tool for Kafka consumer metrics
*/}
73 |
74 |
88 |
89 |
Already have an account? Log in here.
90 |
91 | )
92 | }
93 |
94 | export default Signup;
--------------------------------------------------------------------------------
/src/components/Dual.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {select, selectAll, Selection} from 'd3-selection';
3 | import {scaleLinear, scaleBand} from 'd3-scale';
4 | import {max} from 'd3-array';
5 | import {axisLeft, axisBottom} from 'd3-axis';
6 | import * as d3 from 'd3';
7 |
8 |
9 | ///////////////////////////////////////////////////////
10 |
11 | //local
12 |
13 | // const data = [
14 | // {width: 100, height: 250, col: "wine"},
15 | // {width: 100, height: 100, col: "black"},
16 | // {width: 100, height: 55, col: "yellow"},
17 | // {width: 100, height: 55, col: "burgundy"},
18 | // {width: 100, height: 300, col: "cream"}
19 | // ];
20 |
21 | // const dataa = [
22 | // {timestamp: 100,
23 | // //timestamp: "Monday",
24 | // metric: 'latency', unit:'milliseconds', count: 1000, col:'red'},
25 | // {timestamp:200,
26 | // //timestamp: "Tuesday",
27 | // metric: 'latency', unit:'milliseconds', count: 200, col: 'orange'},
28 | // {timestamp:300,
29 | // //timestamp: "Wednesday",
30 | // metric: 'latency', unit:'milliseconds', count: 342, col: 'yellow'},
31 | // {timestamp: 400,
32 | // //timestamp: "Thursday",
33 | // metric: 'latency', unit:'milliseconds', count: 132, col: 'green'},
34 | // {timestamp: 500,
35 | // //timestamp: "Friday",
36 | // metric: 'latency', unit:'milliseconds', count: 10, col: 'blue'},
37 | // {timestamp: 600,
38 | // //timestamp: "Saturday",
39 | // metric: 'latency', unit:'milliseconds', count: 123, col: 'purple'},
40 | // {timestamp: 700,
41 | // //timestamp: "Sunday",
42 | // metric: 'latency', unit:'milliseconds', count: 550, col: 'black'},
43 |
44 |
45 | // ]
46 |
47 |
48 | let dimensions = {
49 | width: 800,
50 | height: 730,
51 |
52 | chartW: 700,
53 | chartH: 700,
54 |
55 | margin: 70
56 | }
57 |
58 |
59 | ///////////////////////////////////////////////////////
60 | //: React.FC
61 |
62 |
63 | const Dual = (props) => {
64 | let dataconverted = [];
65 |
66 | //let dataconverted = [];
67 | const svgRef = useRef
(null)
68 | const [data, setData] = useState(dataconverted)
69 | //y is .count
70 | //x is .timestamp
71 |
72 | for (const [k, v] of Object.entries(props.dataa)) {
73 |
74 | dataconverted.push({timestamp: k, count: v, col: "green"})
75 |
76 | // dataconverted.forEach(e => {
77 | // if (e.timestamp === k) e.count += v;
78 | // else dataconverted.push({timestamp: k, count: v, col: "green"})
79 | // })
80 | //setData(dataconverted);
81 | }
82 |
83 |
84 |
85 | dataconverted.forEach(e => {console.log(e)}, "!!!!!!!!!!!!!!!!");
86 | console.log(dataconverted, "!!!");
87 | ///////////////////////////////////////////////////////
88 |
89 | const [selection, setSelection] = useState>(null);
90 |
91 | ///////////////////////////////////////////////////////
92 |
93 | let maxValue = max(data, d => d.count) // imported function from d3-array can be used in y and x
94 |
95 | let y = scaleLinear()
96 | .domain([0, max(data, d => d.count) + (max(data, d => d.count)*0.3)!]) //count metric, in this case, latency
97 | .range([dimensions.chartH, 0]) // svg height range
98 |
99 | let x = scaleBand() //divide the range into uniform bands
100 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
101 | .range([0, dimensions.chartW]) //svg width range
102 | //.padding(0.1) //closer to 1 = more space between bars
103 | .paddingInner(0.3)
104 | .paddingOuter(0.3)
105 |
106 | let yAx = axisLeft(y)//.ticks
107 | //.tickFormat((d) => (`${d}`) )
108 | let xAx = axisBottom(x)
109 |
110 | let pathOfLine = 100;
111 | let LineEmUp = d3.line(pathOfLine);
112 | //let path = {fill: 'none', stroke: 'orange'};
113 | let line = d3.line()
114 | .x(d => x(d.timestamp))
115 | .y(d => y(d.count));
116 | ///////////////////////////////////////////////////////
117 |
118 | useEffect(() => {
119 | console.log(select(svgRef.current))
120 |
121 | if(!selection) {
122 | setSelection(select(svgRef.current))
123 | } else {
124 |
125 | selection
126 | .append('rect')
127 | .attr('width', dimensions.width)
128 | .attr('height', dimensions.height)
129 | .attr('fill', "white")
130 |
131 |
132 |
133 |
134 | ///////////////////////////////////////////////////////
135 |
136 | const xAxGroup = selection
137 |
138 | .append('g')
139 | .attr(
140 | 'transform',
141 | `translate(${dimensions.margin}, ${dimensions.chartH})`
142 | )
143 | .call(xAx)
144 |
145 | const yAxGroup = selection
146 |
147 | .append('g')
148 | .attr(
149 | 'transform',
150 | `translate(${dimensions.margin}, 0)`
151 | )
152 | .call(yAx)
153 |
154 |
155 | ///////////////////////////////////////////////////////
156 |
157 | selection
158 | .append('g')
159 | .attr('transform', `translate( ${dimensions.margin}, 0)`) // second arg is ^ or v
160 | .selectAll('rect')
161 | .data(data)
162 | .enter()
163 | .append('rect')
164 | .attr('width', x.bandwidth)
165 | .attr('height', d=> dimensions.chartH - y(d.count))
166 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
167 | //.attr('x', d=>(d.timestamp)!)
168 | .attr('x', d=> {
169 | const xX = x(d.timestamp)
170 | if(xX) {
171 | return xX;
172 | }
173 | return null;
174 | })
175 | .attr('y', d => y(d.count))
176 | .attr('fill', d => d.col)
177 | //y scales the input
178 |
179 | selection
180 | .append('path')
181 | .attr('transform', `translate( ${dimensions.margin + 10}, 0)`)
182 | .attr('fill', 'none')
183 | .attr('stroke', 'yellow')
184 | .attr('stroke-width', 1.1)
185 | .datum(data)
186 | .attr("d", line);
187 |
188 |
189 | ///////////////////////////////////////////////////////
190 |
191 | // const graph = selection
192 | // .selectAll('rect')
193 | // .data(data)
194 | // .attr('width', 100)
195 | // .attr('height', d => d.height)
196 | // .attr('fill', d => d.col)
197 | // .attr('x', (e,i) => i * 100); // horizontally adds 100 to the x axis based on i
198 |
199 | //entering this virtual selection, currently contains 2 bargraphs that do not
200 | //have a available
201 | //.enter modifies that by entering the const graph
202 | // graph
203 | // .enter()
204 | // .append('rect')
205 | // .attr('width', 100)
206 | // .attr('height', d => d.height)
207 | // .attr('fill', d => d.col)
208 | // .attr('x', (e,i) => i * 100);
209 |
210 | ///////////////////////////////////////////////////////
211 |
212 | // .data(data)
213 | // .append('rect')
214 | // .attr('width', d => d.width)
215 | // .attr('height', d => d.height)
216 | // .attr('fill', d => d.col);
217 |
218 | ///////////////////////////////////////////////////////
219 |
220 | //hardcode
221 | // .append('rect')
222 | // .attr('height', 100)
223 | // .attr('width', 200)
224 | // .attr('fill', 'purple');
225 | }
226 |
227 | //select is a wrapper that provides properties and methods
228 | //append
229 | //.current is a reference to the element
230 |
231 | // select(svgRef.current)
232 | // .append('rect')
233 | // .attr('width', 100)
234 | // .attr('height', 100)
235 | // .attr('fill', 'purple')
236 |
237 | // selectAll('rect') //(".className")
238 | // .append('rect')
239 | // .attr('width', 100)
240 | // .attr('height', 100)
241 | // .attr('fill', 'purple')
242 | }, [selection])
243 |
244 | ///////////////////////////////////////////////////////
245 | useEffect(() => {
246 | //find a way to update y axis
247 | if(selection){
248 | y = scaleLinear()
249 | .domain([0, max(data, d => d.count)]) //count metric, in this case, latency
250 | .range([dimensions.chartH, 0]) // svg height range
251 |
252 | x = scaleBand() //divide the range into uniform bands
253 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
254 | .range([0, dimensions.chartW]) //svg width range
255 | //.padding(0.1) //closer to 1 = more space between bars
256 | .paddingInner(0.1)
257 |
258 | yAx = axisLeft(y)//.ticks
259 | .tickFormat((d) => (`${d} messages`) )
260 | xAx = axisBottom(x)
261 |
262 | // xAxGroup = selection
263 |
264 | // .append('g')
265 | // .attr(
266 | // 'transform',
267 | // `translate(${dimensions.margin}, ${dimensions.chartH})`
268 | // )
269 | // .call(xAx)
270 |
271 | // yAxGroup = selection
272 |
273 | // .append('g')
274 | // .attr(
275 | // 'transform',
276 | // `translate(${dimensions.margin}, 0)`
277 | // )
278 | // .call(yAx)
279 |
280 |
281 | const grapheles = selection.selectAll('rect').data(data)
282 |
283 | grapheles
284 | .exit()
285 | .remove()
286 |
287 | grapheles
288 | .attr('transform', `translate( ${dimensions.margin}, 0)`)
289 | .attr('width', x.bandwidth)
290 | .attr('height', d=> dimensions.chartH - y(d.count))
291 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
292 | .attr('x', d=> {
293 | const xX = x(d.timestamp)
294 | if(xX) {
295 | return xX;
296 | }
297 | return null;
298 | })
299 | .attr('y', d => y(d.count))
300 | .attr('fill', d => d.col)
301 |
302 | grapheles
303 | .enter()
304 | .append('rect')
305 | .attr('width', x.bandwidth)
306 | .attr('height', d=> dimensions.chartH - y(d.count))
307 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
308 | .attr('x', d=> {
309 | const xX = x(d.timestamp)
310 | if(xX) {
311 | return xX;
312 | }
313 | return null;
314 | })
315 | .attr('y', d => y(d.count))
316 | .attr('fill', d => d.col)
317 |
318 | }
319 | }, [data])
320 |
321 | const addData = () => {
322 | let dataToAdd = {
323 | timestamp: 'Random',
324 | metric: 'random',
325 | unit: 'random',
326 | count: Math.floor(Math.random() * 300),
327 | col: 'orange'
328 | }
329 | setData([...data, dataToAdd]);
330 | }
331 |
332 | const removeData = () => {
333 | if (data.length === 0) {
334 | return
335 | }
336 | let slicedData = data.slice(0, data.length - 1);
337 | setData(slicedData);
338 | }
339 |
340 |
341 | return (
342 |
343 |
344 |
347 | {/*
348 |
*/}
349 |
350 |
351 | )
352 | }
353 |
354 | {/* */}
359 |
360 | ///////////////////////////////////////////////////////
361 |
362 | //svg inclusions
363 | //
364 | //
365 | //
366 |
367 | export default Dual;
--------------------------------------------------------------------------------
/src/components/Line.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {select, selectAll, Selection} from 'd3-selection';
3 | import {scaleLinear, scaleBand} from 'd3-scale';
4 | import {max} from 'd3-array';
5 | import {axisLeft, axisBottom} from 'd3-axis';
6 | import * as d3 from 'd3';
7 | import { easeCircle } from 'd3';
8 |
9 |
10 | ///////////////////////////////////////////////////////
11 | let dimensions = {
12 |
13 | width: 800,
14 | height: 730,
15 |
16 | chartW: 700,
17 | chartH: 700,
18 |
19 | margin: 70
20 |
21 | }
22 | ///////////////////////////////////////////////////////
23 | //: React.FC
24 |
25 |
26 |
27 | const Line = (props) => {
28 | let arr = [];
29 | let data = [];
30 |
31 | const svgRef = useRef(null)
32 | //const [data, setData] = useState(dataconverted)
33 |
34 | for (const [k, v] of Object.entries(props.dataa)) {
35 | arr.push({timestamp: k, count: v, col: "green"})
36 |
37 | }
38 | data = arr.slice(arr.length-20, arr.length-1);
39 |
40 | const [selection, setSelection] = useState>(null);
41 |
42 | ///////////////////////////////////////////////////////
43 |
44 | let maxValue = max(data, d => d.count) // imported function from d3-array can be used in y and x
45 |
46 | let y = scaleLinear()
47 | .domain([0, max(data, d => d.count) + (max(data, d => d.count)*0.3)]) //count metric, in this case, latency
48 | .range([dimensions.chartH, 0]) // svg height range
49 |
50 | let x = scaleBand() //divide the range into uniform bands
51 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
52 | .range([0, dimensions.chartW]) //svg width range
53 | //.padding(0.1) //closer to 1 = more space between bars
54 | .paddingInner(0.3)
55 | .paddingOuter(0.3)
56 |
57 | let yAx = axisLeft(y);
58 | let xAx= axisBottom(x);
59 | let xAxGroup;
60 | let yAxGroup;
61 |
62 | let pathOfLine ;
63 | let LineEmUp;
64 | //let path = {fill: 'none', stroke: 'orange'};
65 | let line;
66 | ///////////////////////////////////////////////////////
67 |
68 | useEffect(() => {
69 | if(!selection) {
70 | setSelection(select(svgRef.current))
71 | } else {
72 |
73 | y = scaleLinear()
74 | .domain([0, max(data, d => d.count) + (max(data, d => d.count)*0.3)])
75 | .range([dimensions.chartH, 0])
76 |
77 | x = scaleBand()
78 | .domain(data.map(d=>d.timestamp))
79 | .range([0, dimensions.chartW])
80 |
81 | .paddingInner(0.3)
82 | .paddingOuter(0.3)
83 |
84 | yAx = axisLeft(y)
85 | xAx = axisBottom(x)
86 |
87 | selection
88 | .selectAll(".xaxis").remove()
89 |
90 | selection
91 | .selectAll(".yaxis").remove()
92 |
93 | ///////////////////////////////////////////////////////
94 |
95 | xAxGroup = selection
96 | .append('g')
97 | .attr("class","xaxis")
98 | .attr(
99 | 'transform',
100 | `translate(${dimensions.margin}, ${dimensions.chartH})`
101 | )
102 | .call(xAx)
103 |
104 | yAxGroup = selection
105 |
106 | .append('g')
107 | .attr("class","xaxis")
108 | .attr(
109 | 'transform',
110 | `translate(${dimensions.margin}, 0)`
111 | )
112 | .call(yAx)
113 |
114 | selection
115 | .selectAll(".line").remove()
116 |
117 | pathOfLine = 100;
118 | LineEmUp = d3.line()([[10, 60], [40, 90], [60, 10], [190, 10]])
119 | //LineEmUp = d3.line(pathOfLine);
120 | line = d3.line()
121 | .x(d => x(d.timestamp))
122 | .y(d => y(d.count));
123 | ///////////////////////////////////////////////////////
124 | // if (selection.selectAll(".line")[0].length>1) {
125 |
126 | // selection
127 | // .selectAll(".line")
128 | // .datum(data)
129 | // .append('path')
130 | // .transition()
131 | // .duration(1000)
132 | // .attr("class", "line")
133 | // .attr('transform', `translate( ${dimensions.margin}, 0)`)
134 | // .attr('fill', 'none')
135 | // .attr('stroke', 'green')
136 | // .attr('stroke-width', 1.1)
137 | // .attr("d", line);
138 |
139 | // } else {
140 | // selection
141 | // .append('path')
142 | // .attr("class", "line")
143 | // .attr('transform', `translate( ${dimensions.margin}, 0)`)
144 | // .attr('fill', 'none')
145 | // .attr('stroke', 'green')
146 | // .attr('stroke-width', 1.1)
147 | // .datum(data)
148 | // .attr("d", line);
149 |
150 | selection
151 | .append('path')
152 | .attr("class", "line")
153 | .attr('transform', `translate( ${dimensions.margin + 20}, 0)`)
154 | .attr('fill', 'none')
155 | .attr('stroke', 'green')
156 | .attr('stroke-width', 2)
157 | .datum(data)
158 | //.attr("d", LineEmUp)
159 | .transition()
160 | //delay((e,i) => i * 1000)
161 | .duration(1000)
162 | .ease(easeCircle)
163 | //.datum(data)
164 | .attr('stroke-width', 9)
165 | .attr("d",
166 | d3.line()
167 | .x(d => x(d.timestamp))
168 | .y(d => y(d.count))
169 | )
170 | // }
171 | ///////////////////////////////////////////////////////
172 |
173 | // const graph = selection
174 | // .selectAll('rect')
175 | // .data(data)
176 | // .attr('width', 100)
177 | // .attr('height', d => d.height)
178 | // .attr('fill', d => d.col)
179 | // .attr('x', (e,i) => i * 100); // horizontally adds 100 to the x axis based on i
180 |
181 | //entering this virtual selection, currently contains 2 bargraphs that do not
182 | //have a available
183 | //.enter modifies that by entering the const graph
184 | // graph
185 | // .enter()
186 | // .append('rect')
187 | // .attr('width', 100)
188 | // .attr('height', d => d.height)
189 | // .attr('fill', d => d.col)
190 | // .attr('x', (e,i) => i * 100);
191 |
192 | ///////////////////////////////////////////////////////
193 |
194 | // .data(data)
195 | // .append('rect')
196 | // .attr('width', d => d.width)
197 | // .attr('height', d => d.height)
198 | // .attr('fill', d => d.col);
199 |
200 | ///////////////////////////////////////////////////////
201 |
202 | //hardcode
203 | // .append('rect')
204 | // .attr('height', 100)
205 | // .attr('width', 200)
206 | // .attr('fill', 'purple');
207 | }
208 |
209 | //select is a wrapper that provides properties and methods
210 | //append
211 | //.current is a reference to the element
212 |
213 | // select(svgRef.current)
214 | // .append('rect')
215 | // .attr('width', 100)
216 | // .attr('height', 100)
217 | // .attr('fill', 'purple')
218 |
219 | // selectAll('rect') //(".className")
220 | // .append('rect')
221 | // .attr('width', 100)
222 | // .attr('height', 100)
223 | // .attr('fill', 'purple')
224 | }, [selection])
225 |
226 | ///////////////////////////////////////////////////////
227 | useEffect(() => {
228 | //find a way to update y axis
229 | if(selection){
230 |
231 | y = scaleLinear()
232 | .domain([0, max(data, d => d.count)]) //count metric, in this case, latency
233 | .range([dimensions.chartH, 0]) // svg height range
234 |
235 | x = scaleBand() //divide the range into uniform bands
236 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
237 | .range([0, dimensions.chartW]) //svg width range
238 | //.padding(0.1) //closer to 1 = more space between bars
239 | .paddingInner(0.1)
240 |
241 | yAx = axisLeft(y)
242 |
243 | xAx = axisBottom(x)
244 |
245 | selection
246 | .selectAll(".xaxis").remove()
247 |
248 | selection
249 | .selectAll(".yaxis").remove()
250 |
251 | xAxGroup = selection
252 | .append('g')
253 | .attr("class", "xaxis")
254 | .attr(
255 | 'transform',
256 | `translate(${dimensions.margin}, ${dimensions.chartH})`
257 | )
258 | .call(xAx)
259 |
260 | yAxGroup = selection
261 | .append('g')
262 | .attr("class", "yaxis")
263 | .attr(
264 | 'transform',
265 | `translate(${dimensions.margin}, 0)`
266 | )
267 | .call(yAx)
268 |
269 | pathOfLine = -1000;
270 | //LineEmUp = d3.line(pathOfLine);
271 | LineEmUp = d3.line()([[10, 60], [40, 90], [60, 10], [190, 10]])
272 | // linezero = d3.line()
273 | // .x(d => x(d.timestamp))
274 | // .y(d => y(d.count));
275 |
276 | line = d3.line()
277 | .x(d => x(d.timestamp)!)
278 | .y(d => y(d.count)!);
279 |
280 | selection
281 | .selectAll(".line").remove()
282 |
283 | // if (selection.selectAll(".line")[0].length>1) {
284 |
285 | // selection
286 | // .selectAll(".line")
287 | // .append('path')
288 | // .datum(data)
289 | // .transition()
290 | // .duration(1000)
291 | // .ease(easeCircle)
292 | // .attr("class", "line")
293 | // .attr('transform', `translate( ${dimensions.margin}, 0)`)
294 | // .attr('fill', 'none')
295 | // .attr('stroke', 'green')
296 | // .attr('stroke-width', 1.1)
297 | // .attr("d", line);
298 |
299 | // } else {
300 |
301 |
302 | // selection
303 | // .append('path')
304 | // .attr("class", "line")
305 | // .attr('transform', `translate( ${dimensions.margin}, 0)`)
306 | // .attr('fill', 'none')
307 | // .attr('stroke', 'green')
308 | // .attr('stroke-width', 1.1)
309 | // .datum(data)
310 | // .attr("d", line);
311 |
312 | // }
313 |
314 | // ========================================
315 | selection
316 | .append('path')
317 | .attr("class", "line")
318 | .attr('transform', `translate( ${dimensions.margin + 20}, 0)`)
319 | .attr('fill', 'none')
320 | .attr('stroke', 'green')
321 | .attr('stroke-width', 2)
322 | .datum(data)
323 | //.attr("d", LineEmUp)
324 | .transition()
325 | //delay((e,i) => i * 1000)
326 | .duration(1000)
327 | .ease(easeCircle)
328 | //.datum(data)
329 | .attr('stroke-width', 9)
330 | .attr("d",
331 | d3.line()
332 | .x(d => x(d.timestamp))
333 | .y(d => y(d.count))
334 | )
335 |
336 | }
337 | }, [data])
338 |
339 |
340 |
341 | return (
342 |
343 |
344 |
347 | {/*
348 |
*/}
349 |
350 |
351 | )
352 | }
353 |
354 | {/* */}
359 |
360 | ///////////////////////////////////////////////////////
361 |
362 | //svg inclusions
363 | //
364 | //
365 | //
366 |
367 | export default Line;
--------------------------------------------------------------------------------
/src/components/Line2.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {select, selectAll, Selection} from 'd3-selection';
3 | import {scaleLinear, scaleBand} from 'd3-scale';
4 | import {max} from 'd3-array';
5 | import {axisLeft, axisBottom} from 'd3-axis';
6 | import * as d3 from 'd3';
7 | import { easeCircle } from 'd3';
8 |
9 |
10 | ///////////////////////////////////////////////////////
11 |
12 | //local
13 |
14 | // const data = [
15 | // {width: 100, height: 250, col: "wine"},
16 | // {width: 100, height: 100, col: "black"},
17 | // {width: 100, height: 55, col: "yellow"},
18 | // {width: 100, height: 55, col: "burgundy"},
19 | // {width: 100, height: 300, col: "cream"}
20 | // ];
21 |
22 | // const dataa = [
23 | // {timestamp: 100,
24 | // //timestamp: "Monday",
25 | // metric: 'latency', unit:'milliseconds', count: 1000, col:'red'},
26 | // {timestamp:200,
27 | // //timestamp: "Tuesday",
28 | // metric: 'latency', unit:'milliseconds', count: 200, col: 'orange'},
29 | // {timestamp:300,
30 | // //timestamp: "Wednesday",
31 | // metric: 'latency', unit:'milliseconds', count: 342, col: 'yellow'},
32 | // {timestamp: 400,
33 | // //timestamp: "Thursday",
34 | // metric: 'latency', unit:'milliseconds', count: 132, col: 'green'},
35 | // {timestamp: 500,
36 | // //timestamp: "Friday",
37 | // metric: 'latency', unit:'milliseconds', count: 10, col: 'blue'},
38 | // {timestamp: 600,
39 | // //timestamp: "Saturday",
40 | // metric: 'latency', unit:'milliseconds', count: 123, col: 'purple'},
41 | // {timestamp: 700,
42 | // //timestamp: "Sunday",
43 | // metric: 'latency', unit:'milliseconds', count: 550, col: 'black'},
44 |
45 |
46 | // ]
47 |
48 |
49 | let dimensions = {
50 | width: 800,
51 | height: 730,
52 |
53 | chartW: 700,
54 | chartH: 700,
55 |
56 | margin: 70
57 | }
58 |
59 |
60 | ///////////////////////////////////////////////////////
61 | //: React.FC
62 |
63 |
64 | //let data = arr.slice(arr.length-11, arr.length-1)
65 | // interface {
66 |
67 | // }
68 |
69 | const Vis = (props) => {
70 |
71 | let arr = [];
72 | let data = [];
73 |
74 | const svgRef = useRef(null)
75 | // const [data, setData] = useState(dataconverted)
76 | //y is .count
77 | //x is .timestamp
78 |
79 | for (const [k, v] of Object.entries(props.dataa)) {
80 | arr.push({timestamp: k, count: v, col: "brown"})
81 | // console.log("IS THIS EVEN?????????????????")
82 | // if (!data.length) {
83 | // data.push({timestamp: k, count: v, col: "brown"})
84 | // } else {
85 | // data.forEach(e => {
86 | // if (e.timestamp === k) e.count += v;
87 |
88 | // else data.push({timestamp: k, count: v, col: "brown"})
89 | // })
90 | // }
91 | //setData(dataconverted);
92 | }
93 | data = arr.slice(arr.length-20, arr.length-1);
94 |
95 |
96 |
97 |
98 | // data.forEach(e => {console.log(e)}, "!!!!!!!!!!!!!!!!");
99 | // console.log(data, "!!!");
100 | ///////////////////////////////////////////////////////
101 |
102 | const [selection, setSelection] = useState>(null);
103 | //let maxValue = max(data, d => d.count) // imported function from d3-array can be used in y and x
104 |
105 | let y = scaleLinear()
106 | .domain([0, max(data, d => d.count) + (max(data, d => d.count)*0.3)]) //count metric, in this case, latency
107 | .range([dimensions.chartH, 0]) // svg height range
108 |
109 | let x = scaleBand() //divide the range into uniform bands
110 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
111 | .range([0, dimensions.chartW]) //svg width range
112 | //.padding(0.1) //closer to 1 = more space between bars
113 | .paddingInner(0.3)
114 | .paddingOuter(0.3)
115 |
116 | let yAx = axisLeft(y)//.ticks
117 | //.tickFormat((d) => (`${d}`) )
118 | let xAx = axisBottom(x)
119 | let xAxGroup;
120 | let yAxGroup;
121 | ///////////////////////////////////////////////////////
122 |
123 | ///////////////////////////////////////////////////////
124 |
125 | useEffect(() => {
126 |
127 | if(!selection) {
128 | setSelection(select(svgRef.current))
129 | } else {
130 |
131 | y = scaleLinear()
132 | .domain([0, max(data, d => d.count) + (max(data, d => d.count)*0.3)]) //count metric, in this case, latency
133 | .range([dimensions.chartH, 0]) // svg height range
134 |
135 | x = scaleBand() //divide the range into uniform bands
136 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
137 | .range([0, dimensions.chartW]) //svg width range
138 | //.padding(0.1) //closer to 1 = more space between bars
139 | .paddingInner(0.3)
140 | .paddingOuter(0.3)
141 |
142 | yAx = axisLeft(y)//.ticks
143 | //.tickFormat((d) => (`${d}`) )
144 | xAx = axisBottom(x)
145 |
146 | // let pathOfLine = 100;
147 | // let LineEmUp = d3.line(pathOfLine);
148 | // let path = {fill: 'none', stroke: 'orange'};
149 | // let line = d3.line()
150 | // .x(d => x(d.timestamp))
151 | // .y(d => y(d.count));
152 | // console.log(select(svgRef.current))
153 | // let pathOfLine = 100;
154 | // let LineEmUp = d3.line(pathOfLine);
155 |
156 | // selection
157 | // .append('rect')
158 | // .attr('width', dimensions.width)
159 | // .attr('height', dimensions.height)
160 | // .attr('fill', "white")
161 |
162 |
163 |
164 |
165 | ///////////////////////////////////////////////////////
166 | // if (xAxGroup) xAxGroup = null;
167 | // if (yAxGroup) yAxGroup = null;
168 |
169 | selection
170 | .selectAll(".xaxis").remove()
171 | .selectAll(".yaxis").remove()
172 |
173 | xAxGroup = selection
174 |
175 | .append('g')
176 | .attr("class","xaxis")
177 | .attr(
178 | 'transform',
179 | `translate(${dimensions.margin}, ${dimensions.chartH})`
180 | )
181 | .call(xAx)
182 |
183 | yAxGroup = selection
184 |
185 | .append('g')
186 | .attr("class","yaxis")
187 | .attr(
188 | 'transform',
189 | `translate(${dimensions.margin}, 0)`
190 | )
191 | .call(yAx)
192 |
193 |
194 | ///////////////////////////////////////////////////////
195 |
196 | selection
197 | .append('g')
198 | //.attr('transform', `translate( ${dimensions.margin}, 0)`) // second arg is ^ or v
199 | .selectAll('rect')
200 | .data(data)
201 | .enter()
202 | .append('rect')
203 | .attr('x', d=> {
204 | const xX = x(d.timestamp)
205 | if(xX) {
206 | return xX;
207 | }
208 | return null;
209 | })
210 | .attr('width', x.bandwidth)
211 | .attr('y', dimensions.chartH)
212 | .attr('height', 0)
213 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
214 | //.attr('x', d=>(d.timestamp)!)
215 | .attr('fill', d => d.col)
216 |
217 | .transition()
218 | .duration(1000)
219 | // .delay((e,i) => i * 100)
220 | .ease(easeCircle)
221 |
222 |
223 | .attr('y', d => y(d.count))
224 | .attr('height', d=> dimensions.chartH - y(d.count))
225 | // selection
226 | // .append('path')
227 | // .attr('transform', `translate( ${dimensions.margin + 50}, 0)`)
228 | // .attr('fill', 'none')
229 | // .attr('stroke', 'orange')
230 | // .attr('stroke-width', 10)
231 | // .datum(data)
232 | // .attr("d", line);
233 |
234 |
235 | ///////////////////////////////////////////////////////
236 |
237 | // const graph = selection
238 | // .selectAll('rect')
239 | // .data(data)
240 | // .attr('width', 100)
241 | // .attr('height', d => d.height)
242 | // .attr('fill', d => d.col)
243 | // .attr('x', (e,i) => i * 100); // horizontally adds 100 to the x axis based on i
244 |
245 | //entering this virtual selection, currently contains 2 bargraphs that do not
246 | //have a available
247 | //.enter modifies that by entering the const graph
248 | // graph
249 | // .enter()
250 | // .append('rect')
251 | // .attr('width', 100)
252 | // .attr('height', d => d.height)
253 | // .attr('fill', d => d.col)
254 | // .attr('x', (e,i) => i * 100);
255 |
256 | ///////////////////////////////////////////////////////
257 |
258 | // .data(data)
259 | // .append('rect')
260 | // .attr('width', d => d.width)
261 | // .attr('height', d => d.height)
262 | // .attr('fill', d => d.col);
263 |
264 | ///////////////////////////////////////////////////////
265 |
266 | //hardcode
267 | // .append('rect')
268 | // .attr('height', 100)
269 | // .attr('width', 200)
270 | // .attr('fill', 'purple');
271 | }
272 |
273 | //select is a wrapper that provides properties and methods
274 | //append
275 | //.current is a reference to the element
276 |
277 | // select(svgRef.current)
278 | // .append('rect')
279 | // .attr('width', 100)
280 | // .attr('height', 100)
281 | // .attr('fill', 'purple')
282 |
283 | // selectAll('rect') //(".className")
284 | // .append('rect')
285 | // .attr('width', 100)
286 | // .attr('height', 100)
287 | // .attr('fill', 'purple')
288 | }, [selection])
289 |
290 | ///////////////////////////////////////////////////////
291 | useEffect(() => {
292 | //find a way to update y axis
293 | if(selection){
294 |
295 |
296 | // xAxGroup = selection
297 |
298 | // .append('g')
299 | // .attr(
300 | // 'transform',
301 | // `translate(${dimensions.margin}, ${dimensions.chartH})`
302 | // )
303 | // .call(xAx)
304 |
305 | // yAxGroup = selection
306 |
307 | // .append('g')
308 | // .attr(
309 | // 'transform',
310 | // `translate(${dimensions.margin}, 0)`
311 | // )
312 | // .call(yAx)
313 | y = scaleLinear()
314 | .domain([0, max(data, d => d.count) + (max(data, d => d.count)*0.3)]) //count metric, in this case, latency
315 | .range([dimensions.chartH, 0]) // svg height range
316 |
317 | x = scaleBand() //divide the range into uniform bands
318 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
319 | .range([0, dimensions.chartW]) //svg width range
320 | //.padding(0.1) //closer to 1 = more space between bars
321 | .paddingInner(0.3)
322 | .paddingOuter(0.3)
323 |
324 | yAx = axisLeft(y)//.ticks
325 | //.tickFormat((d) => (`${d}`) )
326 | xAx = axisBottom(x)
327 |
328 | // if (xAxGroup) xAxGroup = null;
329 | // if (yAxGroup) yAxGroup = null;
330 | selection
331 | .selectAll(".xaxis").remove()
332 | .selectAll(".yaxis").remove()
333 |
334 |
335 | xAxGroup = selection
336 |
337 | .append('g')
338 | .attr("class", "xaxis")
339 | .attr(
340 | 'transform',
341 | `translate(${dimensions.margin}, ${dimensions.chartH})`
342 | )
343 | .call(xAx)
344 |
345 | yAxGroup = selection
346 |
347 | .append('g')
348 | .attr("class", "yaxis")
349 | .attr(
350 | 'transform',
351 | `translate(${dimensions.margin}, 0)`
352 | )
353 | .call(yAx)
354 |
355 |
356 | let grapheles = selection.selectAll('rect').data(data)
357 |
358 | grapheles
359 | .exit()
360 | .remove()
361 |
362 | // grapheles
363 | // .attr('transform', `translate( ${dimensions.margin}, 0)`)
364 | // .attr('width', x.bandwidth)
365 | // .attr('height', d=> dimensions.chartH - y(d.count))
366 | // //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
367 | // .attr('x', d=> {
368 | // const xX = x(d.timestamp)
369 | // if(xX) {
370 | // return xX;
371 | // }
372 | // return null;
373 | // })
374 | // .attr('y', d => y(d.count))
375 | // .attr('fill', d => d.col)
376 |
377 | grapheles
378 | .attr('transform', `translate( ${dimensions.margin}, 0)`)
379 | .attr('x', d=> {
380 | const xX = x(d.timestamp)
381 | if(xX) {
382 | return xX;
383 | }
384 | return null;
385 | })
386 | .attr('width', x.bandwidth)
387 | .attr('y', d => dimensions.chartH)
388 | .attr('height', 0)
389 | .attr('fill', d => d.col)
390 |
391 | .transition()
392 | .duration(1000)
393 | .ease(easeCircle)
394 |
395 | .attr('y', d => y(d.count))
396 | .attr('height', d=> dimensions.chartH - y(d.count))
397 |
398 | grapheles
399 | .enter()
400 | .append('rect')
401 | .attr('width', x.bandwidth)
402 | .attr('height', d=> dimensions.chartH - y(d.count))
403 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
404 | .attr('x', d=> {
405 | const xX = x(d.timestamp)
406 | if(xX) {
407 | return xX;
408 | }
409 | return null;
410 | })
411 | .attr('y', d => y(d.count))
412 | .attr('fill', d => d.col)
413 |
414 | }
415 | }, [data])
416 |
417 | // const addData = () => {
418 | // let dataToAdd = {
419 | // timestamp: 'Random',
420 | // metric: 'random',
421 | // unit: 'random',
422 | // count: Math.floor(Math.random() * 300),
423 | // col: 'orange'
424 | // }
425 | // setData([...data, dataToAdd]);
426 | // }
427 |
428 | // const removeData = () => {
429 | // if (data.length === 0) {
430 | // return
431 | // }
432 | // let slicedData = data.slice(0, data.length - 1);
433 | // setData(slicedData);
434 | // }
435 |
436 |
437 | return (
438 |
439 |
440 |
443 | {/*
444 |
*/}
445 |
446 |
447 | )
448 | }
449 |
450 | {/* */}
455 |
456 | ///////////////////////////////////////////////////////
457 |
458 | //svg inclusions
459 | //
460 | //
461 | //
462 |
463 | export default Vis;
--------------------------------------------------------------------------------
/src/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const NavBar = () => {
5 | return (
6 |
14 | )
15 | }
16 |
17 | export default NavBar;
--------------------------------------------------------------------------------
/src/components/Testing.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {select, selectAll, Selection} from 'd3-selection';
3 | import {scaleLinear, scaleBand} from 'd3-scale';
4 | import {max} from 'd3-array';
5 | import {axisLeft, axisBottom} from 'd3-axis';
6 | import * as d3 from 'd3';
7 |
8 |
9 | ///////////////////////////////////////////////////////
10 |
11 | //local
12 |
13 | // const data = [
14 | // {width: 100, height: 250, col: "wine"},
15 | // {width: 100, height: 100, col: "black"},
16 | // {width: 100, height: 55, col: "yellow"},
17 | // {width: 100, height: 55, col: "burgundy"},
18 | // {width: 100, height: 300, col: "cream"}
19 | // ];
20 |
21 | const dataa = [
22 | {timestamp: 100,
23 | //timestamp: "Monday",
24 | metric: 'latency', unit:'milliseconds', count: 1000, col:'red'},
25 | {timestamp:200,
26 | //timestamp: "Tuesday",
27 | metric: 'latency', unit:'milliseconds', count: 200, col: 'orange'},
28 | {timestamp:300,
29 | //timestamp: "Wednesday",
30 | metric: 'latency', unit:'milliseconds', count: 342, col: 'yellow'},
31 | {timestamp: 400,
32 | //timestamp: "Thursday",
33 | metric: 'latency', unit:'milliseconds', count: 132, col: 'green'},
34 | {timestamp: 500,
35 | //timestamp: "Friday",
36 | metric: 'latency', unit:'milliseconds', count: 10, col: 'blue'},
37 | {timestamp: 600,
38 | //timestamp: "Saturday",
39 | metric: 'latency', unit:'milliseconds', count: 123, col: 'purple'},
40 | {timestamp: 700,
41 | //timestamp: "Sunday",
42 | metric: 'latency', unit:'milliseconds', count: 550, col: 'black'},
43 |
44 |
45 | ]
46 |
47 |
48 | let dimensions = {
49 | width: 800,
50 | height: 730,
51 |
52 | chartW: 700,
53 | chartH: 700,
54 |
55 | margin: 70
56 | }
57 |
58 |
59 | ///////////////////////////////////////////////////////
60 | //: React.FC
61 |
62 |
63 | let data = [];
64 |
65 | const Testing = () => {
66 |
67 |
68 | //let dataconverted = [];
69 | const svgRef = useRef(null)
70 | const [data, setData] = useState(dataa)
71 | //y is .count
72 | //x is .timestamp
73 |
74 | // for (const [k, v] of Object.entries(props.dataa)) {
75 | // data.push({timestamp: k, count: v, col: "brown"})
76 | // console.log("IS THIS EVEN?????????????????")
77 | // // if (!data.length) {
78 | // // data.push({timestamp: k, count: v, col: "brown"})
79 | // // } else {
80 | // // data.forEach(e => {
81 | // // if (e.timestamp === k) e.count += v;
82 |
83 | // // else data.push({timestamp: k, count: v, col: "brown"})
84 | // // })
85 | // // }
86 | // //setData(dataconverted);
87 | // }
88 |
89 |
90 |
91 |
92 | //data.forEach(e => {console.log(e)}, "!!!!!!!!!!!!!!!!");
93 | //console.log(data, "!!!");
94 | ///////////////////////////////////////////////////////
95 |
96 | const [selection, setSelection] = useState>(null);
97 |
98 | ///////////////////////////////////////////////////////
99 |
100 | let maxValue = max(data, d => d.count) // imported function from d3-array can be used in y and x
101 |
102 | let y = scaleLinear()
103 | .domain([0, max(data, d => d.count) + (max(data, d => d.count)*0.3)!]) //count metric, in this case, latency
104 | .range([dimensions.chartH, 0]) // svg height range
105 |
106 | let x = scaleBand() //divide the range into uniform bands
107 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
108 | .range([0, dimensions.chartW]) //svg width range
109 | //.padding(0.1) //closer to 1 = more space between bars
110 | .paddingInner(0.3)
111 | .paddingOuter(0.3)
112 |
113 | let yAx = axisLeft(y)//.ticks
114 | //.tickFormat((d) => (`${d}`) )
115 | let xAx = axisBottom(x)
116 |
117 | let pathOfLine = 100;
118 | let LineEmUp = d3.line(pathOfLine);
119 | //let path = {fill: 'none', stroke: 'orange'};
120 | // let line = d3.line()
121 | // .x(d => x(d.timestamp))
122 | // .y(d => y(d.count));
123 | ///////////////////////////////////////////////////////
124 |
125 | useEffect(() => {
126 | console.log(select(svgRef.current))
127 |
128 | if(!selection) {
129 | setSelection(select(svgRef.current))
130 | } else {
131 |
132 | // selection
133 | // .append('rect')
134 | // .attr('width', dimensions.width)
135 | // .attr('height', dimensions.height)
136 | // .attr('fill', "white")
137 |
138 |
139 |
140 |
141 | ///////////////////////////////////////////////////////
142 |
143 | const xAxGroup = selection
144 |
145 | .append('g')
146 | .attr(
147 | 'transform',
148 | `translate(${dimensions.margin}, ${dimensions.chartH})`
149 | )
150 | .call(xAx)
151 |
152 | const yAxGroup = selection
153 |
154 | .append('g')
155 | .attr(
156 | 'transform',
157 | `translate(${dimensions.margin}, 0)`
158 | )
159 | .call(yAx)
160 |
161 |
162 | ///////////////////////////////////////////////////////
163 |
164 | selection
165 | .append('g')
166 | .attr('transform', `translate( ${dimensions.margin}, 0)`) // second arg is ^ or v
167 | .selectAll('rect')
168 | .data(data)
169 | .enter()
170 | .append('rect')
171 | .attr('width', x.bandwidth)
172 | .attr('height', d=> dimensions.chartH - y(d.count))
173 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
174 | //.attr('x', d=>(d.timestamp)!)
175 | .attr('x', d=> {
176 | const xX = x(d.timestamp)
177 | if(xX) {
178 | return xX;
179 | }
180 | return null;
181 | })
182 | .attr('y', d => y(d.count))
183 | .attr('fill', d => d.col)
184 | //y scales the input
185 |
186 | // selection
187 | // .append('path')
188 | // .attr('transform', `translate( ${dimensions.margin + 50}, 0)`)
189 | // .attr('fill', 'none')
190 | // .attr('stroke', 'orange')
191 | // .attr('stroke-width', 10)
192 | // .datum(data)
193 | // .attr("d", line);
194 |
195 |
196 | ///////////////////////////////////////////////////////
197 |
198 | // const graph = selection
199 | // .selectAll('rect')
200 | // .data(data)
201 | // .attr('width', 100)
202 | // .attr('height', d => d.height)
203 | // .attr('fill', d => d.col)
204 | // .attr('x', (e,i) => i * 100); // horizontally adds 100 to the x axis based on i
205 |
206 | //entering this virtual selection, currently contains 2 bargraphs that do not
207 | //have a available
208 | //.enter modifies that by entering the const graph
209 | // graph
210 | // .enter()
211 | // .append('rect')
212 | // .attr('width', 100)
213 | // .attr('height', d => d.height)
214 | // .attr('fill', d => d.col)
215 | // .attr('x', (e,i) => i * 100);
216 |
217 | ///////////////////////////////////////////////////////
218 |
219 | // .data(data)
220 | // .append('rect')
221 | // .attr('width', d => d.width)
222 | // .attr('height', d => d.height)
223 | // .attr('fill', d => d.col);
224 |
225 | ///////////////////////////////////////////////////////
226 |
227 | //hardcode
228 | // .append('rect')
229 | // .attr('height', 100)
230 | // .attr('width', 200)
231 | // .attr('fill', 'purple');
232 | }
233 |
234 | //select is a wrapper that provides properties and methods
235 | //append
236 | //.current is a reference to the element
237 |
238 | // select(svgRef.current)
239 | // .append('rect')
240 | // .attr('width', 100)
241 | // .attr('height', 100)
242 | // .attr('fill', 'purple')
243 |
244 | // selectAll('rect') //(".className")
245 | // .append('rect')
246 | // .attr('width', 100)
247 | // .attr('height', 100)
248 | // .attr('fill', 'purple')
249 | }, [selection])
250 |
251 | ///////////////////////////////////////////////////////
252 | useEffect(() => {
253 | //find a way to update y axis
254 | if(selection){
255 | y = scaleLinear()
256 | .domain([0, max(data, d => d.count)]) //count metric, in this case, latency
257 | .range([dimensions.chartH, 0]) // svg height range
258 |
259 | x = scaleBand() //divide the range into uniform bands
260 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
261 | .range([0, dimensions.chartW]) //svg width range
262 | //.padding(0.1) //closer to 1 = more space between bars
263 | .paddingInner(0.1)
264 |
265 | yAx = axisLeft(y)//.ticks
266 | .tickFormat((d) => (`${d} messages`) )
267 | xAx = axisBottom(x)
268 |
269 | // xAxGroup = selection
270 |
271 | // .append('g')
272 | // .attr(
273 | // 'transform',
274 | // `translate(${dimensions.margin}, ${dimensions.chartH})`
275 | // )
276 | // .call(xAx)
277 |
278 | // yAxGroup = selection
279 |
280 | // .append('g')
281 | // .attr(
282 | // 'transform',
283 | // `translate(${dimensions.margin}, 0)`
284 | // )
285 | // .call(yAx)
286 |
287 |
288 | const grapheles = selection.selectAll('rect').data(data)
289 |
290 | grapheles
291 | .exit()
292 | .remove()
293 |
294 | grapheles
295 | .attr('transform', `translate( ${dimensions.margin}, 0)`)
296 | .attr('width', x.bandwidth)
297 | .attr('height', d=> dimensions.chartH - y(d.count))
298 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
299 | .attr('x', d=> {
300 | const xX = x(d.timestamp)
301 | if(xX) {
302 | return xX;
303 | }
304 | return null;
305 | })
306 | .attr('y', d => y(d.count))
307 | .attr('fill', d => d.col)
308 |
309 | grapheles
310 | .enter()
311 | .append('rect')
312 | .attr('width', x.bandwidth)
313 | .attr('height', d=> dimensions.chartH - y(d.count))
314 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
315 | .attr('x', d=> {
316 | const xX = x(d.timestamp)
317 | if(xX) {
318 | return xX;
319 | }
320 | return null;
321 | })
322 | .attr('y', d => y(d.count))
323 | .attr('fill', d => d.col)
324 |
325 | }
326 | }, [data])
327 |
328 | // const addData = () => {
329 | // let dataToAdd = {
330 | // timestamp: 'Random',
331 | // metric: 'random',
332 | // unit: 'random',
333 | // count: Math.floor(Math.random() * 300),
334 | // col: 'orange'
335 | // }
336 | // setData([...data, dataToAdd]);
337 | // }
338 |
339 | // const removeData = () => {
340 | // if (data.length === 0) {
341 | // return
342 | // }
343 | // let slicedData = data.slice(0, data.length - 1);
344 | // setData(slicedData);
345 | // }
346 |
347 |
348 | return (
349 |
350 |
351 |
354 | {/*
355 |
*/}
356 |
357 |
358 | )
359 | }
360 |
361 | {/* */}
366 |
367 | ///////////////////////////////////////////////////////
368 |
369 | //svg inclusions
370 | //
371 | //
372 | //
373 |
374 | export default Testing;
--------------------------------------------------------------------------------
/src/components/Testing2.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {select, selectAll, Selection} from 'd3-selection';
3 | import {scaleLinear, scaleBand} from 'd3-scale';
4 | import {max} from 'd3-array';
5 | import {axisLeft, axisBottom} from 'd3-axis';
6 | import * as d3 from 'd3';
7 | import { easeCircle } from 'd3';
8 |
9 |
10 | ///////////////////////////////////////////////////////
11 |
12 | //local
13 |
14 | // const data = [
15 | // {width: 100, height: 250, col: "wine"},
16 | // {width: 100, height: 100, col: "black"},
17 | // {width: 100, height: 55, col: "yellow"},
18 | // {width: 100, height: 55, col: "burgundy"},
19 | // {width: 100, height: 300, col: "cream"}
20 | // ];
21 |
22 | const dataa = [
23 | {timestamp: 100,
24 | //timestamp: "Monday",
25 | metric: 'latency', unit:'milliseconds', count: 1000, col:'red'},
26 | {timestamp:200,
27 | //timestamp: "Tuesday",
28 | metric: 'latency', unit:'milliseconds', count: 200, col: 'orange'},
29 | {timestamp:300,
30 | //timestamp: "Wednesday",
31 | metric: 'latency', unit:'milliseconds', count: 342, col: 'yellow'},
32 | {timestamp: 400,
33 | //timestamp: "Thursday",
34 | metric: 'latency', unit:'milliseconds', count: 132, col: 'green'},
35 | {timestamp: 500,
36 | //timestamp: "Friday",
37 | metric: 'latency', unit:'milliseconds', count: 10, col: 'blue'},
38 | {timestamp: 600,
39 | //timestamp: "Saturday",
40 | metric: 'latency', unit:'milliseconds', count: 123, col: 'purple'},
41 | {timestamp: 700,
42 | //timestamp: "Sunday",
43 | metric: 'latency', unit:'milliseconds', count: 550, col: 'black'},
44 | {timestamp: 1000,
45 | //timestamp: "Monday",
46 | metric: 'latency', unit:'milliseconds', count: 1000, col:'red'},
47 | {timestamp:2000,
48 | //timestamp: "Tuesday",
49 | metric: 'latency', unit:'milliseconds', count: 200, col: 'orange'},
50 | {timestamp:3000,
51 | //timestamp: "Wednesday",
52 | metric: 'latency', unit:'milliseconds', count: 342, col: 'yellow'},
53 | {timestamp: 4000,
54 | //timestamp: "Thursday",
55 | metric: 'latency', unit:'milliseconds', count: 132, col: 'green'},
56 | {timestamp: 5000,
57 | //timestamp: "Friday",
58 | metric: 'latency', unit:'milliseconds', count: 10, col: 'blue'},
59 | {timestamp: 6000,
60 | //timestamp: "Saturday",
61 | metric: 'latency', unit:'milliseconds', count: 123, col: 'purple'},
62 | {timestamp: 7000,
63 | //timestamp: "Sunday",
64 | metric: 'latency', unit:'milliseconds', count: 550, col: 'black'},
65 | ]
66 |
67 |
68 | let dimensions = {
69 | width: 800,
70 | height: 730,
71 |
72 | chartW: 700,
73 | chartH: 700,
74 |
75 | margin: 70
76 | }
77 |
78 |
79 |
80 |
81 | // const updating = () => {
82 |
83 | // let randomX = Math.floor(Math.random() * 10000);
84 | // let randomY = Math.floor(Math.random() * 1000);
85 | // // {x value: y value }
86 | // let objectobject = {}
87 | // objectobject[randomX] = randomY;
88 | // console.log(`randomX: ${randomX}`);
89 | // console.log(`randomX: ${randomY}`);
90 |
91 | // for (const [k, v] of Object.entries(objectobject)) {
92 | // dataa.push({timestamp: k, metric: 'latency', unit:'milliseconds', count: v, col: "brown"})
93 | // }
94 | // return;
95 | // }
96 |
97 |
98 | // setTimeout((), 3000);
99 | ///////////////////////////////////////////////////////
100 | //: React.FC
101 |
102 |
103 | //let data = [];
104 | // function updating () {
105 |
106 |
107 | // let randomX = Math.floor(Math.random() * 10000);
108 | // let randomY = Math.floor(Math.random() * 1000);
109 | // // {x value: y value }
110 | // let objectobject = {}
111 | // objectobject[randomX] = randomY;
112 | // console.log(`randomX: ${randomX}`);
113 | // console.log(`randomX: ${randomY}`);
114 |
115 | // for (const [k, v] of Object.entries(objectobject)) {
116 | // dataa.push({timestamp: k, metric: 'latency', unit:'milliseconds', count: v, col: "brown"})
117 | // }
118 | // if (renderGraph) setTimeout(() => updating(), 5000);
119 | // }
120 |
121 | const Testing2 = (props) => {
122 |
123 |
124 | //let dataconverted = [];
125 | const svgRef = useRef(null)
126 | const [data, setData] = useState(props.dataa)
127 | // const [renderGraph, setGraph] = useState(hahaprops.render)
128 | //y is .count
129 | //x is .timestamp
130 | // useEffect(() => {
131 | // if (renderGraph === false) {
132 | // updating()
133 | // }
134 | // })
135 | // setTimeout(() => {
136 | // setGraph(false);
137 | // }, 5000);
138 |
139 | // useEffect(() => {
140 | // setTimeout(() => updating(), 5000);
141 | // }, [data])
142 |
143 | // for (const [k, v] of Object.entries(props.dataa)) {
144 | // data.push({timestamp: k, count: v, col: "brown"})
145 | // console.log("IS THIS EVEN?????????????????")
146 | // // if (!data.length) {
147 | // // data.push({timestamp: k, count: v, col: "brown"})
148 | // // } else {
149 | // // data.forEach(e => {
150 | // // if (e.timestamp === k) e.count += v;
151 |
152 | // // else data.push({timestamp: k, count: v, col: "brown"})
153 | // // })
154 | // // }
155 | // //setData(dataconverted);
156 | // }
157 |
158 |
159 |
160 |
161 | //data.forEach(e => {console.log(e)}, "!!!!!!!!!!!!!!!!");
162 | //console.log(data, "!!!");
163 | ///////////////////////////////////////////////////////
164 |
165 | const [selection, setSelection] = useState>(null);
166 |
167 | ///////////////////////////////////////////////////////
168 |
169 | //let maxValue = max(data, d => d.count) // imported function from d3-array can be used in y and x
170 |
171 | let y = scaleLinear()
172 | .domain([0, max(data, d => d.count) + (max(data, d => d.count)*0.3)!]) //count metric, in this case, latency
173 | .range([dimensions.chartH, 0]) // svg height range
174 |
175 | let x = scaleBand() //divide the range into uniform bands
176 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
177 | .range([0, dimensions.chartW]) //svg width range
178 | //.padding(0.1) //closer to 1 = more space between bars
179 | .paddingInner(0.3)
180 | .paddingOuter(0.3)
181 |
182 | let yAx = axisLeft(y)//.ticks
183 | //.tickFormat((d) => (`${d}`) )
184 | let xAx = axisBottom(x)
185 |
186 | let pathOfLine = 100;
187 | let LineEmUp = d3.line(pathOfLine);
188 | //let path = {fill: 'none', stroke: 'orange'};
189 | // let line = d3.line()
190 | // .x(d => x(d.timestamp))
191 | // .y(d => y(d.count));
192 | ///////////////////////////////////////////////////////
193 |
194 | useEffect(() => {
195 | console.log(select(svgRef.current))
196 |
197 | if(!selection) {
198 | setSelection(select(svgRef.current))
199 | } else {
200 |
201 | // selection
202 | // .append('rect')
203 | // .attr('width', dimensions.width)
204 | // .attr('height', dimensions.height)
205 | // .attr('fill', "white")
206 |
207 |
208 |
209 |
210 | ///////////////////////////////////////////////////////
211 |
212 | const xAxGroup = selection
213 |
214 | .append('g')
215 | .attr(
216 | 'transform',
217 | `translate(${dimensions.margin}, ${dimensions.chartH})`
218 | )
219 | .call(xAx)
220 |
221 | const yAxGroup = selection
222 |
223 | .append('g')
224 | .attr(
225 | 'transform',
226 | `translate(${dimensions.margin}, 0)`
227 | )
228 | .call(yAx)
229 |
230 |
231 | ///////////////////////////////////////////////////////
232 |
233 | selection
234 | .append('g')
235 | .attr('transform', `translate( ${dimensions.margin}, 0)`) // second arg is ^ or v
236 | .selectAll('rect')
237 | .data(data)
238 | .enter()
239 | .append('rect')
240 | .attr('x', d=> {
241 | const xX = x(d.timestamp)
242 | if(xX) {
243 | return xX;
244 | }
245 | return null;
246 | })
247 | .attr('width', x.bandwidth)
248 | .attr('y', dimensions.chartH)
249 | .attr('height', 0)
250 |
251 | .transition()
252 | .duration(1000)
253 | // .delay((e,i) => i * 100)
254 | .ease(easeCircle)
255 |
256 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
257 | //.attr('x', d=>(d.timestamp)!)
258 |
259 | .attr('height', d=> dimensions.chartH - y(d.count))
260 | .attr('y', d => y(d.count))
261 | .attr('fill', d => d.col)
262 | //y scales the input
263 |
264 | // selection
265 | // .append('path')
266 | // .attr('transform', `translate( ${dimensions.margin + 50}, 0)`)
267 | // .attr('fill', 'none')
268 | // .attr('stroke', 'orange')
269 | // .attr('stroke-width', 10)
270 | // .datum(data)
271 | // .attr("d", line);
272 |
273 |
274 | ///////////////////////////////////////////////////////
275 |
276 | // const graph = selection
277 | // .selectAll('rect')
278 | // .data(data)
279 | // .attr('width', 100)
280 | // .attr('height', d => d.height)
281 | // .attr('fill', d => d.col)
282 | // .attr('x', (e,i) => i * 100); // horizontally adds 100 to the x axis based on i
283 |
284 | //entering this virtual selection, currently contains 2 bargraphs that do not
285 | //have a available
286 | //.enter modifies that by entering the const graph
287 | // graph
288 | // .enter()
289 | // .append('rect')
290 | // .attr('width', 100)
291 | // .attr('height', d => d.height)
292 | // .attr('fill', d => d.col)
293 | // .attr('x', (e,i) => i * 100);
294 |
295 | ///////////////////////////////////////////////////////
296 |
297 | // .data(data)
298 | // .append('rect')
299 | // .attr('width', d => d.width)
300 | // .attr('height', d => d.height)
301 | // .attr('fill', d => d.col);
302 |
303 | ///////////////////////////////////////////////////////
304 |
305 | //hardcode
306 | // .append('rect')
307 | // .attr('height', 100)
308 | // .attr('width', 200)
309 | // .attr('fill', 'purple');
310 | }
311 |
312 | //select is a wrapper that provides properties and methods
313 | //append
314 | //.current is a reference to the element
315 |
316 | // select(svgRef.current)
317 | // .append('rect')
318 | // .attr('width', 100)
319 | // .attr('height', 100)
320 | // .attr('fill', 'purple')
321 |
322 | // selectAll('rect') //(".className")
323 | // .append('rect')
324 | // .attr('width', 100)
325 | // .attr('height', 100)
326 | // .attr('fill', 'purple')
327 | }, [selection])
328 |
329 | ///////////////////////////////////////////////////////
330 | useEffect(() => {
331 | //find a way to update y axis
332 | if(selection){
333 | y = scaleLinear()
334 | .domain([0, max(data, d => d.count)]) //count metric, in this case, latency
335 | .range([dimensions.chartH, 0]) // svg height range
336 |
337 | x = scaleBand() //divide the range into uniform bands
338 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
339 | .range([0, dimensions.chartW]) //svg width range
340 | //.padding(0.1) //closer to 1 = more space between bars
341 | .paddingInner(0.1)
342 |
343 | yAx = axisLeft(y)//.ticks
344 | .tickFormat((d) => (`${d} messages`) )
345 | xAx = axisBottom(x)
346 |
347 | // xAxGroup = selection
348 |
349 | // .append('g')
350 | // .attr(
351 | // 'transform',
352 | // `translate(${dimensions.margin}, ${dimensions.chartH})`
353 | // )
354 | // .call(xAx)
355 |
356 | // yAxGroup = selection
357 |
358 | // .append('g')
359 | // .attr(
360 | // 'transform',
361 | // `translate(${dimensions.margin}, 0)`
362 | // )
363 | // .call(yAx)
364 |
365 |
366 | const grapheles = selection.selectAll('rect').data(data)
367 |
368 | grapheles
369 | .exit()
370 | .remove()
371 |
372 | grapheles
373 | .attr('transform', `translate( ${dimensions.margin}, 0)`)
374 | .attr('width', x.bandwidth)
375 | .attr('height', d=> dimensions.chartH - y(d.count))
376 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
377 | .attr('x', d=> {
378 | const xX = x(d.timestamp)
379 | if(xX) {
380 | return xX;
381 | }
382 | return null;
383 | })
384 | .attr('y', d => y(d.count))
385 | .attr('fill', d => d.col)
386 |
387 | grapheles
388 | .enter()
389 | .append('rect')
390 | .attr('width', x.bandwidth)
391 | .attr('height', d=> dimensions.chartH - y(d.count))
392 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
393 | .attr('x', d=> {
394 | const xX = x(d.timestamp)
395 | if(xX) {
396 | return xX;
397 | }
398 | return null;
399 | })
400 | .attr('y', d => y(d.count))
401 | .attr('fill', d => d.col)
402 |
403 | }
404 | }, [data])
405 |
406 | // const addData = () => {
407 | // let dataToAdd = {
408 | // timestamp: 'Random',
409 | // metric: 'random',
410 | // unit: 'random',
411 | // count: Math.floor(Math.random() * 300),
412 | // col: 'orange'
413 | // }
414 | // setData([...data, dataToAdd]);
415 | // }
416 |
417 | // const removeData = () => {
418 | // if (data.length === 0) {
419 | // return
420 | // }
421 | // let slicedData = data.slice(0, data.length - 1);
422 | // setData(slicedData);
423 | // }
424 |
425 |
426 | return (
427 |
428 |
429 |
432 | {/*
433 |
*/}
434 |
435 |
436 | )
437 | }
438 |
439 | {/* */}
444 |
445 | ///////////////////////////////////////////////////////
446 |
447 | //svg inclusions
448 | //
449 | //
450 | //
451 |
452 | export default Testing2;
--------------------------------------------------------------------------------
/src/components/UserPage/AccountInfo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AccountInfo = () => {
4 | return (
5 |
6 |
7 |
8 | This will be a list of all previous sessions, pulled from the database, along with any other features we want to add
9 |
10 |
11 | )
12 | }
13 |
14 | export default AccountInfo;
--------------------------------------------------------------------------------
/src/components/UserPage/AddTopicCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const AddTopicCard = () => {
5 | return (
6 |
7 |
Add a new topic
8 |
9 |
Topic name:
10 |
Partitions:
11 |
12 | {/*
*/}
13 |
14 | )
15 | }
16 |
17 | export default AddTopicCard;
--------------------------------------------------------------------------------
/src/components/UserPage/BrokerCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const BrokerCard = () => {
5 | return (
6 |
7 |
Broker
8 |
9 |
Topics: 1
10 |
Partitions: 3
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default BrokerCard;
--------------------------------------------------------------------------------
/src/components/UserPage/BrokerDetails.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useHistory, Switch, Route } from 'react-router-dom';
3 | // @ts-ignore
4 | import BrokerCard from './BrokerCard.tsx';
5 | // @ts-ignore
6 | import LeadMetrics from './LeadMetrics.tsx';
7 | // @ts-ignore
8 | import MessageMetrics from './MessageMetrics.tsx';
9 | // @ts-ignore
10 | import ProducerMetrics from './producerMetrics.tsx';
11 | // @ts-ignore
12 | import ConsumerMetrics from './consumerMetrics.tsx';
13 |
14 | const BrokerDetails = () => {
15 |
16 | const history = useHistory();
17 |
18 | const [topicData, setTopicData] = useState(null);
19 | const [messagesData, setMessagesData] = useState(null);
20 | const [producersData, setproducerData] = useState(null);
21 | const [consumersData, setConsumerData] = useState(null);
22 |
23 | const [leadMetInterval, setLeadMetInterval] = useState(null);
24 | const [messageMetInterval, setMessageMetInterval] = useState(null);
25 | const [producerMetInterval, setProducerMetInterval] = useState(null);
26 | const [consumerMetInterval, setConsumerMetInterval] = useState(null);
27 |
28 | function getTopicData() {
29 | fetch('/kafka/topicData')
30 | .then(data => data.json())
31 | .then(topicsData => {
32 | setTopicData(topicsData)
33 | })
34 | .catch(err => 'Failed to fetch topic data!');
35 | }
36 |
37 | function getMessagesData() {
38 | fetch('/kafka/messageData')
39 | .then(data => data.json())
40 | .then(responseData => {
41 | setMessagesData(responseData);
42 | })
43 | .catch(err => 'Failed to fetch message data!');
44 | }
45 |
46 | function getProducerData() {
47 | fetch('/kafka/producerData')
48 | .then(data => data.json())
49 | .then(producersData => {
50 | setproducerData(producersData);
51 | })
52 | .catch(err => 'Failed to fetch producer data!');
53 | }
54 |
55 | function getConsumerData() {
56 | fetch('/kafka/requestData')
57 | .then(data => data.json())
58 | .then(responseData => {
59 | setConsumerData(responseData);
60 | })
61 | .catch(err => 'Failed to fetch consumer data!');
62 | }
63 |
64 | function handleClick(buttonId) {
65 |
66 | if (leadMetInterval) {
67 | clearInterval(leadMetInterval);
68 | setLeadMetInterval(null);
69 | }
70 | if (messageMetInterval) {
71 | clearInterval(messageMetInterval);
72 | setMessageMetInterval(null);
73 | }
74 | if (producerMetInterval) {
75 | clearInterval(producerMetInterval);
76 | setProducerMetInterval(null);
77 | }
78 | if (consumerMetInterval) {
79 | clearInterval(consumerMetInterval);
80 | setConsumerMetInterval(null);
81 | }
82 |
83 | if (document.getElementById('tabs')) {
84 | document.getElementById('lead-button').style.background = '';
85 | document.getElementById('message-button').style.background = '';
86 | document.getElementById('producer-button').style.background = '';
87 | document.getElementById('consumer-button').style.background = '';
88 | }
89 |
90 |
91 | switch (buttonId) {
92 | case 'lead':
93 | document.getElementById('lead-button').style.background = '#f9eae1';
94 | if (!topicData) getTopicData();
95 | setLeadMetInterval(setInterval(getTopicData, 5000));
96 | history.push('/details/topics')
97 | break;
98 | case 'message':
99 | document.getElementById('message-button').style.background = '#f9eae1';
100 | if (!messagesData) getMessagesData();
101 | setMessageMetInterval(setInterval(getMessagesData, 5000));
102 | history.push('/details/messages')
103 | break;
104 | case 'producer':
105 | document.getElementById('producer-button').style.background = '#f9eae1';
106 | if (!producersData) getProducerData();
107 | setProducerMetInterval(setInterval(getProducerData, 5000));
108 | history.push('/details/producer')
109 | break;
110 | case 'consumer':
111 | document.getElementById('consumer-button').style.background = '#f9eae1';
112 | if (!consumersData) getConsumerData();
113 | setConsumerMetInterval(setInterval(getConsumerData, 5000));
114 | history.push('/details/consumer')
115 | break;
116 | default:
117 | break;
118 | }
119 | }
120 |
121 | return (
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | What data would you like to view?
} />
133 | } />
134 | } />
135 | } />
136 | } />
137 |
138 |
139 |
140 | )
141 | };
142 |
143 |
144 | export default BrokerDetails;
--------------------------------------------------------------------------------
/src/components/UserPage/BrokerOverview.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link } from 'react-router-dom';
3 | // @ts-ignore
4 | import BrokerCard from './BrokerCard.tsx';
5 | // @ts-ignore
6 | import AddTopicCard from './AddTopicCard.tsx';
7 |
8 | const BrokerOverview = () => {
9 |
10 | const [brokerData, setBrokerData] = useState({
11 | random: false,
12 | port: '',
13 | topicData: []
14 | });
15 | const [errorMessage, setErrorMessage] = useState(null);
16 |
17 | const submitPort = e => {
18 | e.preventDefault();
19 |
20 | const port = document.getElementById('port') as HTMLInputElement;
21 |
22 | // *TODO: should check here to make sure the value is a 4-digit number
23 | if (port.value === '') return setErrorMessage('Please enter a valid port number.');
24 |
25 | setBrokerData({ ...brokerData,
26 | port: port.value
27 | })
28 |
29 | port.value = '';
30 | }
31 |
32 | const submitTopicInfo = e => {
33 | e.preventDefault();
34 |
35 | const topicName = document.getElementById('topic-name') as HTMLInputElement;
36 | const partitions = document.getElementById('partitions') as HTMLInputElement;
37 | const repFactor = document.getElementById('replication-factor') as HTMLInputElement;
38 |
39 | if (topicName.value === '' || partitions.value === '' || repFactor.value === '') return setErrorMessage('Please enter topic name, number of partitions, and replication factor.');
40 |
41 |
42 | if (!brokerData || !brokerData.topicData) {
43 | setBrokerData({ ...brokerData,
44 | topicData: [
45 | {
46 | "topicName": topicName.value,
47 | "partition": partitions.value,
48 | "replicationFactor": repFactor.value
49 | }
50 | ]
51 | })
52 | } else {
53 | setBrokerData({ ...brokerData,
54 | topicData: [ ...brokerData.topicData,
55 | {
56 | "topicName": topicName.value,
57 | "partition": partitions.value,
58 | "replicationFactor": repFactor.value
59 | }
60 | ]
61 | })
62 | }
63 |
64 | topicName.value = '';
65 | partitions.value = '';
66 | repFactor.value = '';
67 | }
68 |
69 | // toggle whether to use random data or not
70 | const toggleRandom = () => {
71 | console.log('toggling random');
72 |
73 | if (brokerData.random === true) brokerData.random = false;
74 | else brokerData.random = true;
75 |
76 | console.log(brokerData.random);
77 | }
78 |
79 | // send broker info and view metrics; redirect to /details
80 | const submitBrokerInfo = e => {
81 | // e.preventDefault();
82 | console.log('submitting broker info');
83 |
84 | // check first to make sure there is a port and at least one topic in state
85 | if (!brokerData || brokerData.port === '' || brokerData.topicData === []) {
86 | console.log('Please enter a valid port and at least one topic.');
87 | return setErrorMessage('Please enter a valid port and at least one topic.');
88 | }
89 |
90 | fetchTopic();
91 | console.log('after topic fetch')
92 | fetchConsumer();
93 | console.log('after consumer fetch')
94 | fetchProducer();
95 | console.log('after producer fetch')
96 | }
97 |
98 | // logic for topic data post request
99 | const fetchTopic = () => {
100 | fetch('/kafka/connectTopic', {
101 | method: 'POST',
102 | headers: {
103 | 'Content-Type': 'application/json'
104 | },
105 | body: JSON.stringify({brokerData})
106 | })
107 | .then(response => response.json())
108 | .then(response => {
109 | console.log('result from topic:')
110 | console.log(response)
111 | })
112 | .catch(err => 'Failed to submit topic info!')
113 | console.log('finished topic starting consumer:')
114 | }
115 |
116 | // logic for consumer data post request
117 | const fetchConsumer = () => {
118 | const consumerData = {
119 | port: brokerData.port,
120 | topics: brokerData.topicData.map(topic => topic.topicName)
121 | };
122 |
123 | fetch('/kafka/connectConsumer', {
124 | method: 'POST',
125 | headers: {
126 | 'Content-Type': 'application/json'
127 | },
128 | body: JSON.stringify({consumerData})
129 | })
130 | .then(response => response.json())
131 | .then(response => console.log('consumer function is complete'))
132 | .catch(err => 'Failed to submit topic info!')
133 | }
134 |
135 | // logic for producer data post request
136 | const fetchProducer = () => {
137 | const producerData = {
138 | random: brokerData.random,
139 | port: brokerData.port,
140 | topics: brokerData.topicData.map(topic => topic.topicName)
141 | };
142 |
143 | fetch('/kafka/connectProducer', {
144 | method: 'POST',
145 | headers: {
146 | 'Content-Type': 'application/json'
147 | },
148 | body: JSON.stringify({producerData})
149 | })
150 | .then(response => response.json())
151 | .then(response => console.log(response))
152 | .catch(err => 'Failed to submit topic info!')
153 | }
154 |
155 | // logic to render all topics stored in state
156 | const topicsArray = [];
157 |
158 | for (let i = 0; i < brokerData.topicData.length; i += 1) {
159 | let { topicName, partition, replicationFactor } = brokerData.topicData[i]
160 |
161 | topicsArray.push(
162 |
163 |
Topic name: {topicName}
164 |
Number of partitions: {partition}
165 |
Replication factor: {replicationFactor}
166 |
167 | )
168 | }
169 |
170 | return (
171 |
172 |
173 |
174 |
197 |
198 |
199 |
Current port: {brokerData.port}
200 |
Current topics:
201 |
202 | {topicsArray}
203 |
204 |
205 |
206 |
207 | )
208 | }
209 |
210 | export default BrokerOverview;
211 |
212 | /*
213 |
214 | For topic:
215 |
216 | post request to: /kafka/connectTopic
217 |
218 | req.body = {
219 | "brokerData": {
220 | "port": "9092",
221 | "topicData": [
222 | {
223 | "topicName": "thisIsATest",
224 | "partition": "5",
225 | "replicationFactor": "1"
226 | },
227 | {
228 | "topicName": "thisIsATest1",
229 | "partition": "3",
230 | "replicationFactor": "1"
231 | }
232 | ]
233 | }
234 | }
235 |
236 | For consumer:
237 |
238 | post request to: /kafka/connectConsumer
239 |
240 | req.body = {
241 | "consumerData": {
242 | "port": "9092",
243 | "topics": ["RandomGeneratedData"],
244 | "userId": "3"
245 | }
246 | }
247 |
248 | For producer:
249 |
250 | post request to: /kafka/connectProducer
251 |
252 | req.body = {
253 | "producerData": {
254 | "port": "9092",
255 | "topics": ["RandomGeneratedData"]
256 | }
257 | }
258 |
259 | */
260 |
261 | // ******FORMER BROKER OVERVIEW PAGE LAYOUT******
262 | // return (
263 | //
264 | //
265 | //
266 | // This is where they'll input their server port
267 | //
268 | //
269 | // Never mind
270 | //
273 | //
274 | //
275 | // )
276 | /*
277 | get rid of uuid from users table
278 |
279 | store data with a username and return
280 |
281 | */
--------------------------------------------------------------------------------
/src/components/UserPage/Gallery.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Router, Route, Switch, useLocation } from 'react-router-dom';
3 | // @ts-ignore
4 | import BrokerOverview from './BrokerOverview.tsx';
5 | // @ts-ignore
6 | import BrokerDetails from './BrokerDetails.tsx';
7 | // @ts-ignore
8 | import SessionHistory from './SessionHistory.tsx';
9 |
10 | const Gallery = () => {
11 | return (
12 |
13 |
14 | } />
15 | } />
16 | } />
17 |
18 |
19 | )
20 | }
21 |
22 | export default Gallery;
--------------------------------------------------------------------------------
/src/components/UserPage/LeadMetrics.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | // @ts-ignore
3 | import BrokerCard from './BrokerCard.tsx';
4 | // @ts-ignore
5 | import Vis from '../Vis.tsx';
6 | // @ts-ignore
7 | import Vis2 from '../Vis2.tsx';
8 | // @ts-ignore
9 | import Testing from '../Testing.tsx'
10 |
11 | const LeadMetrics = (props) => {
12 | console.log('props.data:')
13 | console.log(props.data)
14 | if (!props.data) {
15 | return (
16 |
17 |
Key metrics at a glance
18 |
Loading Topics...
19 |
20 | )
21 | } else {
22 |
23 | const topicsArray = [];
24 | for (let index in props.data.partitionQuantity) {
25 | console.log(index)
26 | console.log(props.data.partitionQuantity)
27 | topicsArray.push(
28 |
29 |
Topic: {props.data.partitionQuantity[index].name}
30 |
Partitions: {props.data.partitionQuantity[index].partitionQuantity}
31 |
32 | );
33 | }
34 | return (
35 |
36 |
37 |
Topics and partitions
38 |
39 |
{topicsArray}
40 |
41 |
42 |
43 |
Quantity of messages per partition
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | export default LeadMetrics;
--------------------------------------------------------------------------------
/src/components/UserPage/LeftNav.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const LeftNav = () => {
5 | return (
6 |
11 | )
12 | }
13 |
14 | export default LeftNav;
--------------------------------------------------------------------------------
/src/components/UserPage/MessageMetrics.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | // @ts-ignore
3 | import Vis from '../Vis.tsx';
4 |
5 | const MessageMetrics = (props) => {
6 |
7 | if (!props.data) {
8 | return (
9 |
10 |
11 |
Total messages in consumer
12 |
Loading messages...
13 |
14 |
15 | )
16 | } else {
17 | return (
18 |
19 |
20 |
Latest message:
21 |
{props.data.messageData[props.data.messageData.length - 1].value}
22 |
Partition:
23 |
{props.data.messageData[props.data.messageData.length - 1].partition}
24 |
25 |
26 |
Total messages in consumer
27 |
{props.data.messageCounter}
28 |
29 |
30 | )
31 | }
32 | }
33 |
34 | export default MessageMetrics;
--------------------------------------------------------------------------------
/src/components/UserPage/SessionHistory.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const SessionHistory = () => {
4 | return (
5 |
6 |
7 |
8 | This will be a list of all previous sessions, pulled from the database, along with any other features we want to add
9 |
10 |
11 | )
12 | }
13 |
14 | export default SessionHistory;
--------------------------------------------------------------------------------
/src/components/UserPage/TopNav.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, useHistory } from 'react-router-dom';
3 |
4 | const TopNav = () => {
5 | const history = useHistory();
6 |
7 | const logout = () => {
8 | fetch('/user/logout', { method: 'POST', credentials: 'include' })
9 | .then(data => data.json())
10 | .then(() => history.push('/'))
11 | .catch(err => console.log('ERROR LOGGING OUT: ', err))
12 | }
13 |
14 | return (
15 |
23 | )
24 | }
25 |
26 | export default TopNav;
--------------------------------------------------------------------------------
/src/components/UserPage/UserPage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | // @ts-ignore
4 | import LeftNav from './LeftNav.tsx';
5 | // @ts-ignore
6 | import Gallery from './Gallery.tsx';
7 |
8 | const UserPage = () => {
9 | const history = useHistory();
10 |
11 | useEffect (() => {
12 | fetch('/user/verifySession')
13 | .then(data => data.json())
14 | .then(data => {
15 | if (data === 'failed') history.push('/');
16 | })
17 | }, [])
18 |
19 | return (
20 |
21 |
22 |
23 |
24 | )
25 | }
26 |
27 | export default UserPage;
--------------------------------------------------------------------------------
/src/components/UserPage/consumerMetrics.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | // @ts-ignore
3 | import Vis from '../Vis.tsx';
4 | // @ts-ignore
5 | import Line from '../Line.tsx'
6 |
7 | const ConsumerMetrics = (props) => {
8 |
9 | if (!props.data) {
10 | return (
11 |
12 |
13 |
Consumer metrics
14 |
Loading consumer data...
15 |
16 |
17 | )
18 | } else {
19 |
20 | const quantity = {};
21 | const size = {};
22 |
23 | for (let i = 0; i < props.data.requestData.length; i += 1) {
24 | const message = props.data.requestData[i];
25 | quantity[message.createdAt] = i;
26 | size[i] = message.size;
27 | }
28 |
29 | return (
30 |
31 |
32 |
Consumer metrics
33 |
Total messages received by consumer: {props.data.requestCounter}
34 |
35 |
36 |
37 |
Message quantity over time
38 |
}
39 |
40 |
41 |
42 |
43 |
Message size
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | export default ConsumerMetrics;
--------------------------------------------------------------------------------
/src/components/UserPage/producerMetrics.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | // @ts-ignore
3 | import Vis from '../Vis.tsx';
4 | // @ts-ignore
5 | import Vis2 from '../Vis2.tsx';
6 | // @ts-ignore
7 | import Line from '../Line.tsx';
8 | // @ts-ignore
9 | import Dual from '../Dual.tsx'
10 | // @ts-ignore
11 | import Testing from '../Testing.tsx'
12 | // @ts-ignore
13 | import Testing2 from '../Testing2.tsx'
14 |
15 | const ProducerMetrics = (props) => {
16 |
17 | const [sizeGraphData, setsizeGraphData] = useState(null);
18 | const [timeGraphData, settimeGraphData] = useState(null);
19 |
20 | if (!props.data) {
21 |
22 | return (
23 |
24 |
25 |
Producer metrics
26 |
Loading producer data...
27 |
28 |
29 | )
30 | } else {
31 |
32 | const quantity = {};
33 | const size = {};
34 |
35 | for (let i = 0; i < props.data.producerData.length; i += 1) {
36 | const message = props.data.producerData[i];
37 |
38 | quantity[message.createdAt] = i;
39 |
40 | size[i] = message.size;
41 |
42 |
43 | }
44 |
45 | return (
46 |
47 |
48 |
Producer metrics
49 | {/*
*/}
50 |
Total messages sent by producer: {props.data.producerCounter}
51 | {/*
*/}
52 |
Total messages sent by producer: {props.data.producerCounter}
53 |
54 |
55 |
56 |
Message quantity over time
57 |
58 |
59 |
60 |
61 |
62 |
Message size
63 |
64 |
65 |
66 |
67 | )
68 | }
69 | }
70 | // }
71 |
72 | export default ProducerMetrics;
--------------------------------------------------------------------------------
/src/components/Vis2.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {select, selectAll, Selection} from 'd3-selection';
3 | import {scaleLinear, scaleBand} from 'd3-scale';
4 | import {max} from 'd3-array';
5 | import {axisLeft, axisBottom} from 'd3-axis';
6 | import * as d3 from 'd3';
7 |
8 |
9 | ///////////////////////////////////////////////////////
10 |
11 | //local
12 |
13 | // const data = [
14 | // {width: 100, height: 250, col: "wine"},
15 | // {width: 100, height: 100, col: "black"},
16 | // {width: 100, height: 55, col: "yellow"},
17 | // {width: 100, height: 55, col: "burgundy"},
18 | // {width: 100, height: 300, col: "cream"}
19 | // ];
20 |
21 | // const dataa = [
22 | // {timestamp: 100,
23 | // //timestamp: "Monday",
24 | // metric: 'latency', unit:'milliseconds', count: 1000, col:'red'},
25 | // {timestamp:200,
26 | // //timestamp: "Tuesday",
27 | // metric: 'latency', unit:'milliseconds', count: 200, col: 'orange'},
28 | // {timestamp:300,
29 | // //timestamp: "Wednesday",
30 | // metric: 'latency', unit:'milliseconds', count: 342, col: 'yellow'},
31 | // {timestamp: 400,
32 | // //timestamp: "Thursday",
33 | // metric: 'latency', unit:'milliseconds', count: 132, col: 'green'},
34 | // {timestamp: 500,
35 | // //timestamp: "Friday",
36 | // metric: 'latency', unit:'milliseconds', count: 10, col: 'blue'},
37 | // {timestamp: 600,
38 | // //timestamp: "Saturday",
39 | // metric: 'latency', unit:'milliseconds', count: 123, col: 'purple'},
40 | // {timestamp: 700,
41 | // //timestamp: "Sunday",
42 | // metric: 'latency', unit:'milliseconds', count: 550, col: 'black'},
43 |
44 |
45 | // ]
46 |
47 |
48 | // let dimensions = {
49 | // width: 800,
50 | // height: 730,
51 |
52 | // chartW: 700,
53 | // chartH: 700,
54 |
55 | // margin: 70
56 | // }
57 |
58 |
59 | ///////////////////////////////////////////////////////
60 | //: React.FC
61 |
62 |
63 | let data = [];
64 |
65 | const Vis2 = (props) => {
66 | // console.log("length");
67 | // console.log(props.dataa.length);
68 | let dimensions = {
69 | width: 800,
70 | height: 730,
71 |
72 | chartW: 700,
73 | chartH: 700,
74 |
75 | margin: 70
76 | }
77 |
78 |
79 |
80 | //let dataconverted = [];
81 | const svgRef = useRef(null)
82 | // const [data, setData] = useState(dataconverted)
83 | //y is .count
84 | //x is .timestamp
85 |
86 | for (const [k, v] of Object.entries(props.dataa)) {
87 | data.push({timestamp: k, count: v, col: "brown"})
88 | // console.log("IS THIS EVEN?????????????????")
89 | if (!data.length) {
90 | data.push({timestamp: k, count: v, col: "brown"})
91 | } else {
92 | data.forEach(e => {
93 | if (e.timestamp === k) e.count += v;
94 | else data.push({timestamp: k, count: v, col: "brown"})
95 | })
96 | }
97 |
98 | //setData(dataconverted);
99 | }
100 |
101 |
102 |
103 |
104 | // data.forEach(e => {console.log(e)}, "!!!!!!!!!!!!!!!!");
105 | // console.log(data, "!!!");
106 | ///////////////////////////////////////////////////////
107 |
108 | const [selection, setSelection] = useState>(null);
109 |
110 | ///////////////////////////////////////////////////////
111 |
112 | let maxValue = max(data, d => d.count) // imported function from d3-array can be used in y and x
113 |
114 | let y = scaleLinear()
115 | .domain([0, max(data, d => d.count) + (max(data, d => d.count)*0.3)!]) //count metric, in this case, latency
116 | .range([dimensions.chartH, 0]) // svg height range
117 |
118 | let x = scaleBand() //divide the range into uniform bands
119 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
120 | .range([0, dimensions.chartW]) //svg width range
121 | //.padding(0.1) //closer to 1 = more space between bars
122 | .paddingInner(0.3)
123 | .paddingOuter(0.3)
124 |
125 | let yAx = axisLeft(y)//.ticks
126 | //.tickFormat((d) => (`${d}`) )
127 | let xAx = axisBottom(x)
128 |
129 | let pathOfLine = 100;
130 | let LineEmUp = d3.line(pathOfLine);
131 | //let path = {fill: 'none', stroke: 'orange'};
132 | // let line = d3.line()
133 | // .x(d => x(d.timestamp))
134 | // .y(d => y(d.count));
135 | ///////////////////////////////////////////////////////
136 |
137 | useEffect(() => {
138 | console.log(select(svgRef.current))
139 |
140 | if(!selection) {
141 | setSelection(select(svgRef.current))
142 | } else {
143 |
144 | // selection
145 | // .append('rect')
146 | // .attr('width', dimensions.width)
147 | // .attr('height', dimensions.height)
148 | // .attr('fill', "white")
149 |
150 |
151 |
152 |
153 | ///////////////////////////////////////////////////////
154 |
155 | const xAxGroup = selection
156 |
157 | .append('g')
158 | .attr(
159 | 'transform',
160 | `translate(${dimensions.margin}, ${dimensions.chartH})`
161 | )
162 | .call(xAx)
163 |
164 | const yAxGroup = selection
165 |
166 | .append('g')
167 | .attr(
168 | 'transform',
169 | `translate(${dimensions.margin}, 0)`
170 | )
171 | .call(yAx)
172 |
173 |
174 | ///////////////////////////////////////////////////////
175 |
176 | selection
177 | .append('g')
178 | .attr('transform', `translate( ${dimensions.margin}, 0)`) // second arg is ^ or v
179 | .selectAll('rect')
180 | .data(data)
181 | .enter()
182 | .append('rect')
183 | .attr('width', x.bandwidth)
184 | .attr('height', d=> dimensions.chartH - y(d.count))
185 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
186 | //.attr('x', d=>(d.timestamp)!)
187 | .attr('x', d=> {
188 | const xX = x(d.timestamp)
189 | if(xX) {
190 | return xX;
191 | }
192 | return null;
193 | })
194 | .attr('y', d => y(d.count))
195 | .attr('fill', d => d.col)
196 | //y scales the input
197 |
198 | // selection
199 | // .append('path')
200 | // .attr('transform', `translate( ${dimensions.margin + 50}, 0)`)
201 | // .attr('fill', 'none')
202 | // .attr('stroke', 'orange')
203 | // .attr('stroke-width', 10)
204 | // .datum(data)
205 | // .attr("d", line);
206 |
207 |
208 | ///////////////////////////////////////////////////////
209 |
210 | // const graph = selection
211 | // .selectAll('rect')
212 | // .data(data)
213 | // .attr('width', 100)
214 | // .attr('height', d => d.height)
215 | // .attr('fill', d => d.col)
216 | // .attr('x', (e,i) => i * 100); // horizontally adds 100 to the x axis based on i
217 |
218 | //entering this virtual selection, currently contains 2 bargraphs that do not
219 | //have a available
220 | //.enter modifies that by entering the const graph
221 | // graph
222 | // .enter()
223 | // .append('rect')
224 | // .attr('width', 100)
225 | // .attr('height', d => d.height)
226 | // .attr('fill', d => d.col)
227 | // .attr('x', (e,i) => i * 100);
228 |
229 | ///////////////////////////////////////////////////////
230 |
231 | // .data(data)
232 | // .append('rect')
233 | // .attr('width', d => d.width)
234 | // .attr('height', d => d.height)
235 | // .attr('fill', d => d.col);
236 |
237 | ///////////////////////////////////////////////////////
238 |
239 | //hardcode
240 | // .append('rect')
241 | // .attr('height', 100)
242 | // .attr('width', 200)
243 | // .attr('fill', 'purple');
244 | }
245 |
246 | //select is a wrapper that provides properties and methods
247 | //append
248 | //.current is a reference to the element
249 |
250 | // select(svgRef.current)
251 | // .append('rect')
252 | // .attr('width', 100)
253 | // .attr('height', 100)
254 | // .attr('fill', 'purple')
255 |
256 | // selectAll('rect') //(".className")
257 | // .append('rect')
258 | // .attr('width', 100)
259 | // .attr('height', 100)
260 | // .attr('fill', 'purple')
261 | }, [selection])
262 |
263 | ///////////////////////////////////////////////////////
264 | useEffect(() => {
265 | //find a way to update y axis
266 | if(selection){
267 | y = scaleLinear()
268 | .domain([0, max(data, d => d.count)]) //count metric, in this case, latency
269 | .range([dimensions.chartH, 0]) // svg height range
270 |
271 | x = scaleBand() //divide the range into uniform bands
272 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
273 | .range([0, dimensions.chartW]) //svg width range
274 | //.padding(0.1) //closer to 1 = more space between bars
275 | .paddingInner(0.1)
276 |
277 | yAx = axisLeft(y)//.ticks
278 | .tickFormat((d) => (`${d} messages`) )
279 | xAx = axisBottom(x)
280 |
281 | // xAxGroup = selection
282 |
283 | // .append('g')
284 | // .attr(
285 | // 'transform',
286 | // `translate(${dimensions.margin}, ${dimensions.chartH})`
287 | // )
288 | // .call(xAx)
289 |
290 | // yAxGroup = selection
291 |
292 | // .append('g')
293 | // .attr(
294 | // 'transform',
295 | // `translate(${dimensions.margin}, 0)`
296 | // )
297 | // .call(yAx)
298 |
299 |
300 | const grapheles = selection.selectAll('rect').data(data)
301 |
302 | grapheles
303 | .exit()
304 | .remove()
305 |
306 | grapheles
307 | .attr('transform', `translate( ${dimensions.margin}, 0)`)
308 | .attr('width', x.bandwidth)
309 | .attr('height', d=> dimensions.chartH - y(d.count))
310 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
311 | .attr('x', d=> {
312 | const xX = x(d.timestamp)
313 | if(xX) {
314 | return xX;
315 | }
316 | return null;
317 | })
318 | .attr('y', d => y(d.count))
319 | .attr('fill', d => d.col)
320 |
321 | grapheles
322 | .enter()
323 | .append('rect')
324 | .attr('width', x.bandwidth)
325 | .attr('height', d=> dimensions.chartH - y(d.count))
326 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
327 | .attr('x', d=> {
328 | const xX = x(d.timestamp)
329 | if(xX) {
330 | return xX;
331 | }
332 | return null;
333 | })
334 | .attr('y', d => y(d.count))
335 | .attr('fill', d => d.col)
336 |
337 | }
338 | }, [data])
339 |
340 | // const addData = () => {
341 | // let dataToAdd = {
342 | // timestamp: 'Random',
343 | // metric: 'random',
344 | // unit: 'random',
345 | // count: Math.floor(Math.random() * 300),
346 | // col: 'orange'
347 | // }
348 | // setData([...data, dataToAdd]);
349 | // }
350 |
351 | // const removeData = () => {
352 | // if (data.length === 0) {
353 | // return
354 | // }
355 | // let slicedData = data.slice(0, data.length - 1);
356 | // setData(slicedData);
357 | // }
358 |
359 |
360 | return (
361 |
362 |
363 |
366 | {/*
367 |
*/}
368 |
369 |
370 | )
371 | }
372 |
373 | {/* */}
378 |
379 | ///////////////////////////////////////////////////////
380 |
381 | //svg inclusions
382 | //
383 | //
384 | //
385 |
386 | export default Vis2;
--------------------------------------------------------------------------------
/src/components/goodnessgracious/Library.tsx:
--------------------------------------------------------------------------------
1 |
2 | const Library = () = {
3 |
4 |
5 | return (
6 |
7 | )
8 | }
9 |
10 | export default Library;
--------------------------------------------------------------------------------
/src/components/goodnessgracious/Line.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {select, selectAll, Selection} from 'd3-selection';
3 | import {scaleLinear, scaleBand} from 'd3-scale';
4 |
5 | ///////////////////////////////////////////////////////
6 |
7 | //local
8 |
9 | // const data = [
10 | // {width: 100, height: 250, col: "wine"},
11 | // {width: 100, height: 100, col: "black"},
12 | // {width: 100, height: 55, col: "yellow"},
13 | // {width: 100, height: 55, col: "burgundy"},
14 | // {width: 100, height: 300, col: "cream"}
15 | // ];
16 |
17 | const data = [
18 | {timestamp: "Monday", metric: 'latency', count: 100, col:'red'},
19 | {timestamp: "Tuesday", metric: 'latency', count: 200, col: 'orange'},
20 | {timestamp: "Wednesday", metric: 'latency', count: 342, col: 'yellow'},
21 | {timestamp: "Thursday", metric: 'latency', count: 132, col: 'green'},
22 | {timestamp: "Friday", metric: 'latency', count: 632, col: 'blue'},
23 | {timestamp: "Saturday", metric: 'latency', count: 123, col: 'purple'},
24 | {timestamp: "Sunday", metric: 'latency', count: 550, col: 'white'}
25 | ]
26 |
27 | ///////////////////////////////////////////////////////
28 | //: React.FC
29 | const Line = () => {
30 | const svgRef = useRef(null)
31 |
32 | ///////////////////////////////////////////////////////
33 |
34 | const [selection, setSelection] = useState>(null);
35 |
36 | ///////////////////////////////////////////////////////
37 |
38 | const y = scaleLinear()
39 | .domain([0,1000]) //count metric, in this case, latency
40 | .range([0,1000]) // svg height range
41 |
42 | const x = scaleBand() //divide the range into uniform bands
43 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
44 | .range([0,700]) //svg width range
45 | .padding(0.5) //closer to 1 = more space between bars
46 | //.paddingInner
47 | //.paddingOuter
48 |
49 | ///////////////////////////////////////////////////////
50 |
51 | useEffect(() => {
52 | console.log(select(svgRef.current))
53 |
54 | if(!selection) {
55 | setSelection(select(svgRef.current))
56 | } else {
57 |
58 | selection
59 | .selectAll('rect')
60 | .data(data)
61 | .enter()
62 | .append('rect')
63 | .attr('width', 100)
64 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
65 | .attr('x', d=> {
66 | const xX = x(d.timestamp)
67 | if(xX) {
68 | return xX;
69 | }
70 | return null;
71 | })
72 | .attr('fill', d => d.col)
73 | .attr('height', d=>y(d.count))
74 | //y scales the input
75 |
76 | ///////////////////////////////////////////////////////
77 |
78 | // const graph = selection
79 | // .selectAll('rect')
80 | // .data(data)
81 | // .attr('width', 100)
82 | // .attr('height', d => d.height)
83 | // .attr('fill', d => d.col)
84 | // .attr('x', (e,i) => i * 100); // horizontally adds 100 to the x axis based on i
85 |
86 | //entering this virtual selection, currently contains 2 bargraphs that do not
87 | //have a available
88 | //.enter modifies that by entering the const graph
89 | // graph
90 | // .enter()
91 | // .append('rect')
92 | // .attr('width', 100)
93 | // .attr('height', d => d.height)
94 | // .attr('fill', d => d.col)
95 | // .attr('x', (e,i) => i * 100);
96 |
97 | ///////////////////////////////////////////////////////
98 |
99 | // .data(data)
100 | // .append('rect')
101 | // .attr('width', d => d.width)
102 | // .attr('height', d => d.height)
103 | // .attr('fill', d => d.col);
104 |
105 | ///////////////////////////////////////////////////////
106 |
107 | //hardcode
108 | // .append('rect')
109 | // .attr('height', 100)
110 | // .attr('width', 200)
111 | // .attr('fill', 'purple');
112 | }
113 |
114 | //select is a wrapper that provides properties and methods
115 | //append
116 | //.current is a reference to the element
117 |
118 | // select(svgRef.current)
119 | // .append('rect')
120 | // .attr('width', 100)
121 | // .attr('height', 100)
122 | // .attr('fill', 'purple')
123 |
124 | // selectAll('rect') //(".className")
125 | // .append('rect')
126 | // .attr('width', 100)
127 | // .attr('height', 100)
128 | // .attr('fill', 'purple')
129 | }, [selection])
130 |
131 | ///////////////////////////////////////////////////////
132 |
133 | return (
134 |
135 |
line
136 |
139 |
140 | )
141 | }
142 |
143 | {/* */}
148 |
149 | ///////////////////////////////////////////////////////
150 |
151 | //svg inclusions
152 | //
153 | //
154 | //
155 |
156 | //export default LineChar
--------------------------------------------------------------------------------
/src/components/goodnessgracious/LineChart.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {select, selectAll, Selection} from 'd3-selection';
3 | import {scaleLinear, scaleBand} from 'd3-scale';
4 | import {max} from 'd3-array';
5 | import {axisLeft, axisBottom} from 'd3-axis';
6 |
7 | const dataa = [
8 | {timestamp: "100", metric: 'latency', unit:'milliseconds', count: 1000, col:'red'},
9 | {timestamp: "200", metric: 'latency', unit:'milliseconds', count: 200, col: 'orange'},
10 | {timestamp: "300", metric: 'latency', unit:'milliseconds', count: 342, col: 'yellow'},
11 | {timestamp: "400", metric: 'latency', unit:'milliseconds', count: 132, col: 'green'},
12 | {timestamp: "500", metric: 'latency', unit:'milliseconds', count: 10, col: 'blue'},
13 | {timestamp: "600", metric: 'latency', unit:'milliseconds', count: 123, col: 'purple'},
14 | {timestamp: "700", metric: 'latency', unit:'milliseconds', count: 550, col: 'black'}
15 | ]
16 |
17 | let dimensions = {
18 | width: 1000,
19 | height: 1000,
20 |
21 | chartW: 700,
22 | chartH: 700,
23 |
24 | margin: 50
25 | }
26 |
27 | //x = timestamp
28 | //y =
29 | const Vis = () => {
30 | const svgRef = useRef(null)
31 |
32 | ///////////////////////////////////////////////////////
33 |
34 | const [selection, setSelection] = useState>(null);
35 | const [data, setData] = useState(dataa)
36 |
37 | ///////////////////////////////////////////////////////
38 |
39 | let maxValue = max(data, d => d.count) // imported function from d3-array can be used in y and x
40 |
41 | let y = scaleLinear()
42 | .domain([0, max(data, d => d.count)!]) //count metric, in this case, latency
43 | .range([dimensions.chartH, 0]) // svg height range
44 |
45 | let x = scaleBand() //divide the range into uniform bands
46 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
47 | .range([0, dimensions.chartW]) //svg width range
48 | //.padding(0.1) //closer to 1 = more space between bars
49 | .paddingInner(0.1)
50 | //.paddingOuter
51 |
52 | let yAx = axisLeft(y)//.ticks
53 | //.tickFormat((d) => (`${d}`) )
54 | let xAx = axisBottom(x)
55 | ///////////////////////////////////////////////////////
56 |
57 | useEffect(() => {
58 | console.log(select(svgRef.current))
59 |
60 | if(!selection) {
61 | setSelection(select(svgRef.current))
62 | } else {
63 |
64 | selection
65 | .append('rect')
66 | .attr('width', dimensions.width)
67 | .attr('height', dimensions.height)
68 | .attr('fill', "white")
69 |
70 | ///////////////////////////////////////////////////////
71 |
72 | const xAxGroup = selection
73 |
74 | .append('g')
75 | .attr(
76 | 'transform',
77 | `translate(${dimensions.margin}, ${dimensions.chartH})`
78 | )
79 | .call(xAx)
80 |
81 | const yAxGroup = selection
82 |
83 | .append('g')
84 | .attr(
85 | 'transform',
86 | `translate(${dimensions.margin}, 0)`
87 | )
88 | .call(yAx)
89 |
90 |
91 | ///////////////////////////////////////////////////////
92 |
93 | selection
94 | .append('g')
95 | .attr('transform', `translate( ${dimensions.margin}, 0)`) // second arg is ^ or v
96 | .selectAll('rect')
97 | .data(data)
98 | .enter()
99 | .append('rect')
100 | .attr('width', x.bandwidth)
101 | .attr('height', d=> dimensions.chartH - y(d.count))
102 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
103 | //.attr('x', d=>(d.timestamp)!)
104 | .attr('x', d=> {
105 | const xX = x(d.timestamp)
106 | if(xX) {
107 | return xX;
108 | }
109 | return null;
110 | })
111 | .attr('y', d => y(d.count))
112 | .attr('fill', d => d.col)
113 | //y scales the input
114 |
115 | }, [selection])
116 |
117 |
118 | const BasicLineChart = (props: BasicLineChartProps) => {
119 | const [selection, setSelection] = useState>(null);
120 | const [data, setData] = useState(dataa)
121 |
122 |
123 | useEffect(() => {
124 | draw()
125 | })
126 | const draw = () => {
127 |
128 | //dimensions of the chart
129 | let width = props.width - props.left - props.right
130 | let height = props.height - props.top - props.bottom
131 |
132 | let svg = selection
133 | .select('.basicLineChart')
134 | .append('svg')
135 | .attr('width', width + props.left + props.right)
136 | .attr('height', height + props.top + props.bottom)
137 | .append('g')
138 | .attr('transform', `translate(${props.left},${props.top})`)
139 |
140 | let x = d3
141 | .scaleTime()
142 | .domain([0, max(data, d => d.count)!])
143 | .domain(
144 | d3.extent(data, d => {
145 | return d.timestamp
146 | }) as [Date, Date]
147 | )
148 | .range([0, dimensions.chartW])
149 | svg.append('g')
150 | .attr('transform', `translate(0, ${chartH})`)
151 | .call(d3.axisBottom(x))
152 |
153 | let y = d3
154 | .scaleLinear()
155 | .domain([0, max(data, d => d.count)!])
156 | .range([0, dimensions.chartH])
157 | svg.append('g').call(d3.axisLeft(y))
158 |
159 | svg
160 | .append('path')
161 | .data(data)
162 | .attr('fill', 'none')
163 | .attr('stroke', props.fill)
164 | .attr('stroke-width', 1.5)
165 | .attr(
166 | 'd',
167 | // @ts-ignore
168 | d3
169 | .line()
170 | .x((d) => {
171 | return x(d.timestamp)
172 | })
173 | .y((d) => {
174 | return y(d.count)
175 | })
176 | )
177 | }
178 | }
179 |
180 | interface BasicLineChartProps {
181 | width: number
182 | height: number
183 | top: number
184 | right: number
185 | bottom: number
186 | left: number
187 | fill: string
188 | }
--------------------------------------------------------------------------------
/src/components/goodnessgracious/Vis2.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {select, selectAll, Selection} from 'd3-selection';
3 | import {scaleLinear, scaleBand} from 'd3-scale';
4 | import {max} from 'd3-array';
5 | //mport {axisLeft, axisBottom} from 'd3-axis';
6 |
7 |
8 | ///////////////////////////////////////////////////////
9 |
10 | //local
11 |
12 | // const data = [
13 | // {width: 100, height: 250, col: "wine"},
14 | // {width: 100, height: 100, col: "black"},
15 | // {width: 100, height: 55, col: "yellow"},
16 | // {width: 100, height: 55, col: "burgundy"},
17 | // {width: 100, height: 300, col: "cream"}
18 | // ];
19 |
20 | const dataa = [
21 | {timestamp: "Monday", metric: 'latency', unit:'milliseconds', count: 1000, col:'red'},
22 | {timestamp: "Tuesday", metric: 'latency', unit:'milliseconds', count: 200, col: 'orange'},
23 | {timestamp: "Wednesday", metric: 'latency', unit:'milliseconds', count: 342, col: 'yellow'},
24 | {timestamp: "Thursday", metric: 'latency', unit:'milliseconds', count: 132, col: 'green'},
25 | {timestamp: "Friday", metric: 'latency', unit:'milliseconds', count: 10, col: 'blue'},
26 | {timestamp: "Saturday", metric: 'latency', unit:'milliseconds', count: 123, col: 'purple'},
27 | {timestamp: "Sunday", metric: 'latency', unit:'milliseconds', count: 550, col: 'black'}
28 | ]
29 |
30 | let dimensions = {
31 | width: 1000,
32 | height: 1000,
33 |
34 | chartW: 700,
35 | chartH: 700,
36 |
37 | margin: 70
38 | }
39 |
40 | ///////////////////////////////////////////////////////
41 | //: React.FC
42 | const Vis = () => {
43 | const svgRef = useRef(null)
44 |
45 | ///////////////////////////////////////////////////////
46 |
47 | const [selection, setSelection] = useState>(null);
48 | const [data, setData] = useState(dataa)
49 |
50 | ///////////////////////////////////////////////////////
51 |
52 | let maxValue = max(data, d => d.count) // imported function from d3-array can be used in y and x
53 |
54 | let y = scaleLinear()
55 | .domain([0, max(data, d => d.count)!]) //count metric, in this case, latency
56 | .range([dimensions.height, 0]) // svg height range
57 |
58 | let x = scaleBand() //divide the range into uniform bands
59 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
60 | .range([0, dimensions.width]) //svg width range
61 | //.padding(0.1) //closer to 1 = more space between bars
62 | .paddingInner(0.1)
63 | //.paddingOuter
64 |
65 | // let yAx = axisLeft(y)//.ticks
66 | // //.tickFormat((d) => (`${d}`) )
67 | // let xAx = axisBottom(x)
68 | ///////////////////////////////////////////////////////
69 |
70 | useEffect(() => {
71 | console.log(select(svgRef.current))
72 |
73 | if(!selection) {
74 | setSelection(select(svgRef.current))
75 | } else {
76 |
77 | // selection
78 | // .append('rect')
79 | // .attr('width', dimensions.width)
80 | // .attr('height', dimensions.height)
81 | // .attr('fill', "white")
82 |
83 | ///////////////////////////////////////////////////////
84 |
85 | // const xAxGroup = selection
86 |
87 | // .append('g')
88 | // .attr(
89 | // 'transform',
90 | // `translate(${dimensions.margin}, ${dimensions.chartH})`
91 | // )
92 | // .call(xAx)
93 |
94 | // const yAxGroup = selection
95 |
96 | // .append('g')
97 | // .attr(
98 | // 'transform',
99 | // `translate(${dimensions.margin}, 0)`
100 | // )
101 | // .call(yAx)
102 |
103 |
104 | ///////////////////////////////////////////////////////
105 |
106 | selection
107 | // .append('g')
108 | // .attr('transform', `translate( ${dimensions.margin}, 0)`) // second arg is ^ or v
109 | .selectAll('rect')
110 | .data(data)
111 | .enter()
112 | .append('rect')
113 | .attr('width', x.bandwidth)
114 | .attr('height', (d) => ( dimensions.height - y(d.count) ))
115 | //.attr('x', d =>x(d.timestamp)!) // typescript ignores possiblity of null d.timestamp
116 | //.attr('x', d=>(d.timestamp)!)
117 | .attr('x', d=> {
118 | const xX = x(d.timestamp)
119 | if(xX) {
120 | return xX;
121 | }
122 | return null;
123 | })
124 | .attr('y', d => y(d.count))
125 | .attr('fill', d => d.col)
126 | //y scales the input
127 |
128 |
129 | ///////////////////////////////////////////////////////
130 |
131 | // const graph = selection
132 | // .selectAll('rect')
133 | // .data(data)
134 | // .attr('width', 100)
135 | // .attr('height', d => d.height)
136 | // .attr('fill', d => d.col)
137 | // .attr('x', (e,i) => i * 100); // horizontally adds 100 to the x axis based on i
138 |
139 | //entering this virtual selection, currently contains 2 bargraphs that do not
140 | //have a available
141 | //.enter modifies that by entering the const graph
142 | // graph
143 | // .enter()
144 | // .append('rect')
145 | // .attr('width', 100)
146 | // .attr('height', d => d.height)
147 | // .attr('fill', d => d.col)
148 | // .attr('x', (e,i) => i * 100);
149 |
150 | ///////////////////////////////////////////////////////
151 |
152 | // .data(data)
153 | // .append('rect')
154 | // .attr('width', d => d.width)
155 | // .attr('height', d => d.height)
156 | // .attr('fill', d => d.col);
157 |
158 | ///////////////////////////////////////////////////////
159 |
160 | //hardcode
161 | // .append('rect')
162 | // .attr('height', 100)
163 | // .attr('width', 200)
164 | // .attr('fill', 'purple');
165 | }
166 |
167 | //select is a wrapper that provides properties and methods
168 | //append
169 | //.current is a reference to the element
170 |
171 | // select(svgRef.current)
172 | // .append('rect')
173 | // .attr('width', 100)
174 | // .attr('height', 100)
175 | // .attr('fill', 'purple')
176 |
177 | // selectAll('rect') //(".className")
178 | // .append('rect')
179 | // .attr('width', 100)
180 | // .attr('height', 100)
181 | // .attr('fill', 'purple')
182 | }, [selection])
183 |
184 | ///////////////////////////////////////////////////////
185 | useEffect(() => {
186 | //find a way to update y axis
187 | if(selection){
188 | x = scaleBand() //divide the range into uniform bands
189 | .domain(data.map(d=>d.timestamp)) //domain accepts unique identifiers for divison
190 | .range([0, dimensions.width]) //svg width range
191 | .padding(0.1) //closer to 1 = more space between bars
192 |
193 | y = scaleLinear()
194 | .domain([0, max(data, d => d.count)!]) //count metric, in this case, latency
195 | .range([dimensions.height, 0]) // svg height range
196 |
197 | // yAx = axisLeft(y)//.ticks
198 | // .tickFormat((d) => (`${d}`) )
199 | // xAx = axisBottom(x)
200 |
201 | // xAxGroup = selection
202 |
203 | // .append('g')
204 | // .attr(
205 | // 'transform',
206 | // `translate(${dimensions.margin}, ${dimensions.chartH})`
207 | // )
208 | // .call(xAx)
209 |
210 | // yAxGroup = selection
211 |
212 | // .append('g')
213 | // .attr(
214 | // 'transform',
215 | // `translate(${dimensions.margin}, 0)`
216 | // )
217 | // .call(yAx)
218 |
219 |
220 | const grapheles = selection.selectAll('rect').data(data)
221 |
222 | grapheles.exit().remove()
223 |
224 | grapheles
225 | .attr('width', x.bandwidth)
226 | .attr('height', d=> dimensions.height - y(d.count))
227 | .attr('x', d=> x(d.timestamp)!)
228 | .attr('y', d => y(d.count))
229 | .attr('fill', 'orange')
230 |
231 | grapheles
232 | .enter()
233 | .append('rect')
234 | .attr('width', x.bandwidth)
235 | .attr('height', d=> dimensions.height - y(d.count))
236 | .attr('x', d=> x(d.timestamp)!)
237 | .attr('y', d => y(d.count))
238 | .attr('fill', 'orange')
239 |
240 | }
241 | }, [data])
242 |
243 | const addData = () => {
244 | const dataToAdd = {
245 | timestamp: 'Random',
246 | metric: 'random',
247 | unit: 'random',
248 | count: Math.floor(Math.random() * 300),
249 | col: 'orange'
250 | }
251 | setData([...data, dataToAdd]);
252 | }
253 |
254 | const removeData = () => {
255 | if (data.length === 0) {
256 | return
257 | }
258 | const slicedData = data.slice(0, data.length - 1);
259 | setData(slicedData);
260 | }
261 |
262 |
263 | return (
264 |
265 |
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
266 |
269 |
270 |
271 |
272 |
273 | )
274 | }
275 |
276 | {/* */}
281 |
282 | ///////////////////////////////////////////////////////
283 |
284 | //svg inclusions
285 | //
286 | //
287 | //
288 |
289 | //export default Vis;
--------------------------------------------------------------------------------
/src/components/goodnessgracious/types.ts:
--------------------------------------------------------------------------------
1 |
2 | export namespace Types {
3 | export type Data = {
4 | date: string,
5 | value: number
6 | }
7 | }
--------------------------------------------------------------------------------
/src/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/deKaf/1658de7054650a5d22b1f383b7e6d06154944e23/src/favicon.png
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | deKaf
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { BrowserRouter as Router } from 'react-router-dom';
4 | // @ts-ignore
5 | import App from './components/App.tsx';
6 | // import styles for webpack, this is where we'd import a logo, and append it
7 | import './styles.scss';
8 |
9 | render(
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | );
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["es6", "dom"],
4 | "outDir": "./build/",
5 | "module": "commonjs",
6 | "target": "es6",
7 | "jsx": "preserve",
8 | "allowJs": true,
9 | "moduleResolution": "node",
10 | "allowSyntheticDefaultImports": true,
11 | "esModuleInterop": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"],
15 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 |
5 | // mode set to currect process
6 | mode: process.env.NODE_ENV,
7 | //entry point for compiling
8 | entry: './src/index.tsx',
9 | output: {
10 | //path to our build directory
11 | path: path.resolve(__dirname, 'build'),
12 | //compiled program file name
13 | filename: 'bundle.js',
14 | },
15 | devServer: {
16 | publicPath: '/build',
17 | // Requests proxied to localhost:3000 when in dev build
18 | proxy: {
19 | "/": "http://localhost:3000"
20 | },
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.(ts|tsx)$/,
26 | exclude: /node_modules/,
27 | use: {
28 | // Translates React to js
29 | loader: 'babel-loader',
30 | options: {
31 | presets: ['@babel/preset-env','@babel/preset-react', '@babel/preset-typescript']
32 | }
33 | }
34 | },
35 | {
36 | test: /\.s[ac]ss$/,
37 | exclude: /node_modules/,
38 | use: [
39 | 'style-loader',
40 | 'css-loader',
41 | 'sass-loader'
42 | ]
43 | },
44 | {
45 | test: /\.(gif|png|jpe?g|svg)$/i,
46 | use: {
47 | // Compiles images, if we want to use a logo later on
48 | loader: 'file-loader',
49 | }
50 | }
51 | ]
52 | }
53 | }
--------------------------------------------------------------------------------